module Network.OpenID.AttributeExchange ( AXFieldTy(..), AXFieldVal, axName, axSpec, axTyFromName, axEmailRequired, axExtParams, axExtParams', getAxFields ) where import Control.Applicative import Control.Monad (guard) import Network.OpenID.Types import Data.Maybe (listToMaybe, mapMaybe, fromMaybe) import Data.List (intercalate, nub, isPrefixOf) defaultAlias :: String defaultAlias = "ax" extNamespace, extNamespacePrefix, extMode_fetchRequest :: String extNamespace = "http://openid.net/srv/ax/1.0" extNamespacePrefix = "http://openid.net/srv/ax/1." extMode_fetchRequest = "fetch_request" -- | Some common, useful Attribute Exchange specs. data AXFieldTy = AXBirthdate | AXEmail | AXFirstName | AXFullName | AXGender | AXLanguage | AXLastName | AXNickname deriving (Eq, Show, Ord, Read) axName :: AXFieldTy -> String axName AXBirthdate = "birthdate" axName AXEmail = "email" axName AXFirstName = "firstname" axName AXFullName = "fullname" axName AXGender = "gender" axName AXLanguage = "language" axName AXLastName = "lastname" axName AXNickname = "friendly" axSpec :: AXFieldTy -> String axSpec AXBirthdate = "http://axschema.org/birthDate" axSpec AXEmail = "http://axschema.org/contact/email" axSpec AXFirstName = "http://axschema.org/namePerson/first" axSpec AXFullName = "http://axschema.org/namePerson" axSpec AXGender = "http://axschema.org/person/gender" axSpec AXLanguage = "http://axschema.org/pref/language" axSpec AXLastName = "http://axschema.org/namePerson/last" axSpec AXNickname = "http://axschema.org/namePerson/friendly" axTyFromName :: String -> Maybe AXFieldTy axTyFromName "birthdate" = Just AXBirthdate axTyFromName "email" = Just AXEmail axTyFromName "firstname" = Just AXFirstName axTyFromName "fullname" = Just AXFullName axTyFromName "gender" = Just AXGender axTyFromName "language" = Just AXLanguage axTyFromName "lastname" = Just AXLastName axTyFromName "friendly" = Just AXNickname axTyFromName _ = Nothing -- | Used to store responses. type AXFieldVal = (AXFieldTy, String) -- | The simplest use case is to request the user's email. This would be -- used to replace traditional verification emails. axEmailRequired :: Params axEmailRequired = axExtParams [AXEmail] -- | Use these functions to roll your own list of fields to request when -- you send an auth request axExtParams :: [AXFieldTy] -- ^ params we want them to send in the -- "id_res" mode verification -> Params axExtParams = axExtParams' defaultAlias -- | specify the alias as well as the list of requested fields axExtParams' :: String -- ^ alias. it doesn't really matter -- what this is as long as we're -- consistent -> [AXFieldTy] -- ^ params we require in the "id_res" -- mode verification -> Params axExtParams' alias extsRequired = [ ("openid.ns." ++ alias, extNamespace) , ("openid." ++ alias ++ ".mode", extMode_fetchRequest) , ("openid." ++ alias ++ ".required", formatRequiredVal extsRequired') ] ++ exts where exts = map (formatRequestField alias) extsRequired' extsRequired' = nub extsRequired formatRequestField :: String -> AXFieldTy -> (String, String) formatRequestField alias field = ("openid." ++ alias ++ ".type." ++ axName field, axSpec field) formatRequiredVal :: [AXFieldTy] -> String formatRequiredVal = intercalate "," . map axName -- | Retrieve the requested fields from the HTTP request params. Keep -- | in mind the spec does not require that the OpenID Provider return -- | any of our requested fields, even on a successful verification. getAxFields :: Params -> [AXFieldVal] getAxFields ps = fromMaybe [] fieldsMb where fieldsMb :: Maybe [AXFieldVal] fieldsMb = getAxFields' ps <$> aliasMb aliasMb :: Maybe String aliasMb = listToMaybe $ mapMaybe getAxAlias ps getAxFields' :: Params -> String -> [AXFieldVal] getAxFields' ps alias = mapMaybe getAxFieldTypes' ps where getAxFieldTypes' :: (String, String) -> Maybe AXFieldVal getAxFieldTypes' (n,v) = do guard (valueAliasPrefix `isPrefixOf` n) ty <- axTyFromName $ drop (length valueAliasPrefix) n return (ty,v) valueAliasPrefix = "openid." ++ alias ++ ".value." -- the server will return a response of the form -- openid.ALIAS.value.email: "foobar" -- openid.ALIAS.type.email: "http://axschema.org/contact/email" -- ... -- -- the value of the alias may vary from request to request, depending -- on how many extensions are enabled, but we haven't seen them vary -- the fields. for example, if we requested the email, with the field -- alias "email" in the request then it comes back under value.email -- and type.email. the other libraries I've seen assume this as well -- so we'll just go with that. getAxAlias :: (String, String) -> Maybe String getAxAlias (n,v) = do guard ("openid.ns." `isPrefixOf` n && extNamespacePrefix `isPrefixOf` v) return (drop (length "openid.ns.") n)