{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings  #-}

-- |
-- Module      : Network.OAuth.Type.Credentials
-- Copyright   : (c) Joseph Abrahamson 2013
-- License     : MIT
--
-- Maintainer  : me@jspha.com
-- Stability   : experimental
-- Portability : non-portable
--
-- Credentials, 'Cred's, are built from 'Token's, public/private key pairs, and
-- come in 3 varieties.
--
-- - 'Client': Represents a particular client or consumer, used as part of
-- every transaction that client signs.
--
-- - 'Temporary': Resource token representing a short-lived grant to access a
-- restricted set of server resources on behalf of the user. Typically used as
-- part of a authorization negotiation protocol.
--
-- - 'Permanent': Resource token representing a long-lived grant to access an
-- authorized set of server resources on behalf of the user. Outside of access
-- negotiation this is the most common kind of resource 'Token'.

-- 'Token's are constructed freely from public/private pairs and have
-- 'FromJSON' instances for easy retreival. 'Cred's are more strictly
-- controlled and must be constructed out of a 'Client' 'Token' and
-- (optionally) some kind of resource 'Token'.

module Network.OAuth.Types.Credentials (
  -- * Tokens and their parameterization
  Token (..), Key, Secret, Client, Temporary, Permanent, ResourceToken,

  -- ** Deserialization
  fromUrlEncoded,

  -- * Credentials and credential construction
  Cred, clientCred, temporaryCred, permanentCred, upgradeCred,

  -- * Accessors
  key, secret, clientToken, resourceToken, getResourceTokenDef, signingKey
  ) where

import           Control.Applicative
import           Control.Monad
import           Data.Aeson
import qualified Data.ByteString      as S
import           Data.Data
import           Data.Monoid
import           Network.HTTP.Types   (parseQuery, urlEncode)
import           Network.OAuth.Util
import           Data.Text.Encoding (decodeLatin1, encodeUtf8)

-- Constructors aren't exported. They're only used for derivation
-- purposes.

-- | 'Client' 'Cred'entials and 'Token's are assigned to a particular client by
-- the server and are used for all requests sent by that client. They form the
-- core component of resource specific credentials.
data Client    = Client    deriving ( Data, Typeable )

-- | 'Temporary' 'Token's and 'Cred'entials are created during authorization
-- protocols and are rarely meant to be kept for more than a few minutes.
-- Typically they are authorized to access only a very select set of server
-- resources. During \"three-legged authorization\" in OAuth 1.0 they are used
-- to generate the authorization request URI the client sends and, after that,
-- in the 'Permanent' 'Token' request.
data Temporary = Temporary deriving ( Data, Typeable )

-- | 'Permanent' 'Token's and 'Cred'entials are the primary means of accessing
-- server resources. They must be maintained by the client for each user who
-- authorizes that client to access resources on their behalf.
data Permanent = Permanent deriving ( Data, Typeable )

-- | 'Token' 'Key's are public keys which allow a server to uniquely identify a
-- particular 'Token'.
type Key    = S.ByteString

-- | 'Token' 'Secret's are private keys which the 'Token' uses for
-- cryptographic purposes.
type Secret = S.ByteString

-- | 'Token's are public, private key pairs and come in many varieties,
-- 'Client', 'Temporary', and 'Permanent'.
data Token ty = Token {-# UNPACK #-} !Key
                      {-# UNPACK #-} !Secret
  deriving ( Show, Eq, Ord, Data, Typeable )

class ResourceToken tk where
  upgradeCred' :: Token tk -> Cred tk' -> Cred tk
  upgradeCred' tok (Cred         k s  ) = CredAndToken k s tok
  upgradeCred' tok (CredAndToken k s _) = CredAndToken k s tok

instance ResourceToken Temporary
instance ResourceToken Permanent

upgradeCred :: ResourceToken tk => Token tk -> Cred tk' -> Cred tk
upgradeCred = upgradeCred'

-- | Parses a JSON object with keys @oauth_token@ and @oauth_token_secret@, the
-- standard format for OAuth 1.0.
instance FromJSON (Token ty) where
  parseJSON = withObject "OAuth Token" $ \o ->
    Token <$> fmap encodeUtf8 (o .: "oauth_token")
          <*> fmap encodeUtf8 (o .: "oauth_token_secret")

-- | Produces a JSON object using keys named @oauth_token@ and
-- @oauth_token_secret@.
instance ToJSON (Token ty) where
  toJSON (Token k s) = object [ "oauth_token"        .= (decodeLatin1 k)
                              , "oauth_token_secret" .= (decodeLatin1 s)
                              ]

-- | Parses a @www-form-urlencoded@ stream to produce a 'Token' if possible.
-- The first result value is whether or not the token data is OAuth 1.0a
-- compatible.
--
-- >>> fromUrlEncoded "oauth_token=key&oauth_token_secret=secret"
-- Just (False, Token "key" "secret")
--
-- >>> fromUrlEncoded "oauth_token=key&oauth_token_secret=secret&oauth_callback_confirmed=true"
-- Just (True, Token "key" "secret")
--
fromUrlEncoded :: S.ByteString -> Maybe (Bool, Token ty)
fromUrlEncoded = tryParse . parseQuery where
  tryParse q = do
    tok <- Token <$> lookupV "oauth_token"        q
                 <*> lookupV "oauth_token_secret" q
    confirmed <- lookupV "oauth_callback_confirmed" q <|> pure ""
    return (confirmed == "true", tok)

  lookupV k = join . lookup k

-- | Lens on the key component of a 'Token'.
key :: Functor f => (Key -> f Key) -> Token ty -> f (Token ty)
key inj (Token k s) = (`Token` s) <$> inj k
{-# INLINE key #-}

-- | Lens on the key secret component of a 'Token'.
secret :: Functor f => (Secret -> f Secret) -> Token ty -> f (Token ty)
secret inj (Token k s) = Token k <$> inj s
{-# INLINE secret #-}

-- | 'Cred'entials pair a 'Client' 'Token' and either a 'Temporary' or
-- 'Permanent' token corresponding to a particular set of user
-- resources on the server.
data Cred ty = Cred         {-# UNPACK #-} !Key {-# UNPACK #-} !Secret
             | CredAndToken {-# UNPACK #-} !Key {-# UNPACK #-} !Secret {-# UNPACK #-} !(Token ty)
  deriving ( Show, Eq, Ord, Data, Typeable )

-- | A lens on the client 'Token' in any 'Cred'.
clientToken :: Functor f => (Token Client -> f (Token Client)) -> Cred ty -> f (Cred ty)
clientToken inj (Cred k s) = fixUp <$> inj (Token k s) where
  fixUp (Token k' s') = Cred k' s'
clientToken inj (CredAndToken k s tok) = fixUp <$> inj (Token k s) where
  fixUp (Token k' s') = CredAndToken k' s' tok
{-# INLINE clientToken #-}

-- | A lens focused on the resource 'Token' when available. The only
-- instances of 'ResourceToken' are 'Temporary' and 'Permanent'. This can
-- be used to upgrade 'Temporary' 'Cred's to 'Permanent' 'Cred's.
resourceToken
  :: (ResourceToken ty, ResourceToken ty', Functor f) =>
     (Token ty -> f (Token ty')) -> Cred ty -> f (Cred ty')
resourceToken inj (CredAndToken k s tok) = CredAndToken k s <$> inj tok
{-# INLINE resourceToken #-}

-- | OAuth assumes that, by default, any credential has a resource 'Token' that
-- is by default completely blank. In this way we can talk about the resource
-- 'Token' of even 'Client' 'Cred's.
--
-- >>> getResourceTokenDef (clientCred $ Token "key" "secret")
-- Token "" ""
getResourceTokenDef :: Cred ty -> Token ty
getResourceTokenDef Cred{}                 = Token "" ""
getResourceTokenDef (CredAndToken _ _ tok) = tok

clientCred :: Token Client -> Cred Client
clientCred (Token k s) = Cred k s

temporaryCred :: Token Temporary -> Cred Client -> Cred Temporary
temporaryCred tok (Cred         k s  ) = CredAndToken k s tok

permanentCred :: Token Permanent -> Cred Client -> Cred Permanent
permanentCred tok (Cred         k s  ) = CredAndToken k s tok

-- | Produce a 'signingKey' from a set of credentials. This is a URL
-- encoded string built from the client secret and the token
-- secret.
--
-- If no token secret exists then the blank string is used.
--
-- prop> \secret -> signingKey (clientCred $ Token "key" secret) == (pctEncode secret <> "&" <> "")
signingKey :: Cred ty -> S.ByteString
signingKey (Cred _ clSec) = urlEncode True clSec <> "&" <> ""
signingKey (CredAndToken _ clSec (Token _ tkSec)) =
  pctEncode clSec <> "&" <> pctEncode tkSec
