-- | Bindings Authorization part of The OAuth 2.0 Authorization Framework
-- RFC6749 <https://www.rfc-editor.org/rfc/rfc6749>
module Network.OAuth.OAuth2.AuthorizationRequest where

import Data.Aeson
import Data.Function (on)
import Data.List qualified as List
import Data.Text (Text)
import Data.Text.Encoding qualified as T
import Lens.Micro (over)
import Network.OAuth.OAuth2.Internal
import URI.ByteString
import Prelude hiding (error)

--------------------------------------------------

-- * Authorization Request Errors

--------------------------------------------------

-- | Authorization Code Grant Error Responses https://tools.ietf.org/html/rfc6749#section-4.1.2.1
--
-- I found hard time to figure a way to test the authorization error flow
-- When anything wrong in @/authorize@ request, it will stuck at the Provider page
-- hence no way for this library to parse error response.
-- In other words, @/authorize@ ends up with 4xx or 5xx.
-- Revisit this whenever find a case OAuth2 provider redirects back to Relying party with errors.
data AuthorizationResponseError = AuthorizationResponseError
  { AuthorizationResponseError -> AuthorizationResponseErrorCode
authorizationResponseError :: AuthorizationResponseErrorCode
  , AuthorizationResponseError -> Maybe Text
authorizationResponseErrorDescription :: Maybe Text
  , AuthorizationResponseError -> Maybe (URIRef Absolute)
authorizationResponseErrorUri :: Maybe (URIRef Absolute)
  }
  deriving (Int -> AuthorizationResponseError -> ShowS
[AuthorizationResponseError] -> ShowS
AuthorizationResponseError -> String
(Int -> AuthorizationResponseError -> ShowS)
-> (AuthorizationResponseError -> String)
-> ([AuthorizationResponseError] -> ShowS)
-> Show AuthorizationResponseError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AuthorizationResponseError -> ShowS
showsPrec :: Int -> AuthorizationResponseError -> ShowS
$cshow :: AuthorizationResponseError -> String
show :: AuthorizationResponseError -> String
$cshowList :: [AuthorizationResponseError] -> ShowS
showList :: [AuthorizationResponseError] -> ShowS
Show, AuthorizationResponseError -> AuthorizationResponseError -> Bool
(AuthorizationResponseError -> AuthorizationResponseError -> Bool)
-> (AuthorizationResponseError
    -> AuthorizationResponseError -> Bool)
-> Eq AuthorizationResponseError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AuthorizationResponseError -> AuthorizationResponseError -> Bool
== :: AuthorizationResponseError -> AuthorizationResponseError -> Bool
$c/= :: AuthorizationResponseError -> AuthorizationResponseError -> Bool
/= :: AuthorizationResponseError -> AuthorizationResponseError -> Bool
Eq)

data AuthorizationResponseErrorCode
  = InvalidRequest
  | UnauthorizedClient
  | AccessDenied
  | UnsupportedResponseType
  | InvalidScope
  | ServerError
  | TemporarilyUnavailable
  | UnknownErrorCode Text
  deriving (Int -> AuthorizationResponseErrorCode -> ShowS
[AuthorizationResponseErrorCode] -> ShowS
AuthorizationResponseErrorCode -> String
(Int -> AuthorizationResponseErrorCode -> ShowS)
-> (AuthorizationResponseErrorCode -> String)
-> ([AuthorizationResponseErrorCode] -> ShowS)
-> Show AuthorizationResponseErrorCode
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AuthorizationResponseErrorCode -> ShowS
showsPrec :: Int -> AuthorizationResponseErrorCode -> ShowS
$cshow :: AuthorizationResponseErrorCode -> String
show :: AuthorizationResponseErrorCode -> String
$cshowList :: [AuthorizationResponseErrorCode] -> ShowS
showList :: [AuthorizationResponseErrorCode] -> ShowS
Show, AuthorizationResponseErrorCode
-> AuthorizationResponseErrorCode -> Bool
(AuthorizationResponseErrorCode
 -> AuthorizationResponseErrorCode -> Bool)
-> (AuthorizationResponseErrorCode
    -> AuthorizationResponseErrorCode -> Bool)
-> Eq AuthorizationResponseErrorCode
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AuthorizationResponseErrorCode
-> AuthorizationResponseErrorCode -> Bool
== :: AuthorizationResponseErrorCode
-> AuthorizationResponseErrorCode -> Bool
$c/= :: AuthorizationResponseErrorCode
-> AuthorizationResponseErrorCode -> Bool
/= :: AuthorizationResponseErrorCode
-> AuthorizationResponseErrorCode -> Bool
Eq)

instance FromJSON AuthorizationResponseErrorCode where
  parseJSON :: Value -> Parser AuthorizationResponseErrorCode
parseJSON = String
-> (Text -> Parser AuthorizationResponseErrorCode)
-> Value
-> Parser AuthorizationResponseErrorCode
forall a. String -> (Text -> Parser a) -> Value -> Parser a
withText String
"parseJSON AuthorizationResponseErrorCode" ((Text -> Parser AuthorizationResponseErrorCode)
 -> Value -> Parser AuthorizationResponseErrorCode)
-> (Text -> Parser AuthorizationResponseErrorCode)
-> Value
-> Parser AuthorizationResponseErrorCode
forall a b. (a -> b) -> a -> b
$ \Text
t ->
    AuthorizationResponseErrorCode
-> Parser AuthorizationResponseErrorCode
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (AuthorizationResponseErrorCode
 -> Parser AuthorizationResponseErrorCode)
-> AuthorizationResponseErrorCode
-> Parser AuthorizationResponseErrorCode
forall a b. (a -> b) -> a -> b
$ case Text
t of
      Text
"invalid_request" -> AuthorizationResponseErrorCode
InvalidRequest
      Text
"unauthorized_client" -> AuthorizationResponseErrorCode
UnauthorizedClient
      Text
"access_denied" -> AuthorizationResponseErrorCode
AccessDenied
      Text
"unsupported_response_type" -> AuthorizationResponseErrorCode
UnsupportedResponseType
      Text
"invalid_scope" -> AuthorizationResponseErrorCode
InvalidScope
      Text
"server_error" -> AuthorizationResponseErrorCode
ServerError
      Text
"temporarily_unavailable" -> AuthorizationResponseErrorCode
TemporarilyUnavailable
      Text
_ -> Text -> AuthorizationResponseErrorCode
UnknownErrorCode Text
t

instance FromJSON AuthorizationResponseError where
  parseJSON :: Value -> Parser AuthorizationResponseError
parseJSON = String
-> (Object -> Parser AuthorizationResponseError)
-> Value
-> Parser AuthorizationResponseError
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"parseJSON AuthorizationResponseError" ((Object -> Parser AuthorizationResponseError)
 -> Value -> Parser AuthorizationResponseError)
-> (Object -> Parser AuthorizationResponseError)
-> Value
-> Parser AuthorizationResponseError
forall a b. (a -> b) -> a -> b
$ \Object
t -> do
    AuthorizationResponseErrorCode
authorizationResponseError <- Object
t Object -> Key -> Parser AuthorizationResponseErrorCode
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"error"
    Maybe Text
authorizationResponseErrorDescription <- Object
t Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"error_description"
    Maybe (URIRef Absolute)
authorizationResponseErrorUri <- Object
t Object -> Key -> Parser (Maybe (URIRef Absolute))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"error_uri"
    AuthorizationResponseError -> Parser AuthorizationResponseError
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure AuthorizationResponseError {Maybe Text
Maybe (URIRef Absolute)
AuthorizationResponseErrorCode
authorizationResponseError :: AuthorizationResponseErrorCode
authorizationResponseErrorDescription :: Maybe Text
authorizationResponseErrorUri :: Maybe (URIRef Absolute)
authorizationResponseError :: AuthorizationResponseErrorCode
authorizationResponseErrorDescription :: Maybe Text
authorizationResponseErrorUri :: Maybe (URIRef Absolute)
..}

--------------------------------------------------

-- * URLs

--------------------------------------------------

-- | See 'authorizationUrlWithParams'
authorizationUrl :: OAuth2 -> URI
authorizationUrl :: OAuth2 -> URIRef Absolute
authorizationUrl = QueryParams -> OAuth2 -> URIRef Absolute
authorizationUrlWithParams []

-- | Prepare the authorization URL.  Redirect to this URL
-- asking for user interactive authentication.
--
-- @since 2.6.0
authorizationUrlWithParams :: QueryParams -> OAuth2 -> URI
authorizationUrlWithParams :: QueryParams -> OAuth2 -> URIRef Absolute
authorizationUrlWithParams QueryParams
qs OAuth2
oa = ASetter (URIRef Absolute) (URIRef Absolute) QueryParams QueryParams
-> (QueryParams -> QueryParams)
-> URIRef Absolute
-> URIRef Absolute
forall s t a b. ASetter s t a b -> (a -> b) -> s -> t
over ((Query -> Identity Query)
-> URIRef Absolute -> Identity (URIRef Absolute)
forall a (f :: * -> *).
Functor f =>
(Query -> f Query) -> URIRef a -> f (URIRef a)
queryL ((Query -> Identity Query)
 -> URIRef Absolute -> Identity (URIRef Absolute))
-> ((QueryParams -> Identity QueryParams)
    -> Query -> Identity Query)
-> ASetter
     (URIRef Absolute) (URIRef Absolute) QueryParams QueryParams
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (QueryParams -> Identity QueryParams) -> Query -> Identity Query
Lens' Query QueryParams
queryPairsL) (QueryParams -> QueryParams -> QueryParams
forall a. [a] -> [a] -> [a]
++ QueryParams
queryParts) (OAuth2 -> URIRef Absolute
oauth2AuthorizeEndpoint OAuth2
oa)
  where
    queryParts :: QueryParams
queryParts =
      ((ByteString, ByteString) -> (ByteString, ByteString) -> Bool)
-> QueryParams -> QueryParams
forall a. (a -> a -> Bool) -> [a] -> [a]
List.nubBy (ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
(==) (ByteString -> ByteString -> Bool)
-> ((ByteString, ByteString) -> ByteString)
-> (ByteString, ByteString)
-> (ByteString, ByteString)
-> Bool
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` (ByteString, ByteString) -> ByteString
forall a b. (a, b) -> a
fst) (QueryParams -> QueryParams) -> QueryParams -> QueryParams
forall a b. (a -> b) -> a -> b
$
        QueryParams
qs
          QueryParams -> QueryParams -> QueryParams
forall a. [a] -> [a] -> [a]
++ [ (ByteString
"client_id", Text -> ByteString
T.encodeUtf8 (Text -> ByteString) -> Text -> ByteString
forall a b. (a -> b) -> a -> b
$ OAuth2 -> Text
oauth2ClientId OAuth2
oa)
             , (ByteString
"response_type", ByteString
"code")
             , (ByteString
"redirect_uri", URIRef Absolute -> ByteString
forall a. URIRef a -> ByteString
serializeURIRef' (URIRef Absolute -> ByteString) -> URIRef Absolute -> ByteString
forall a b. (a -> b) -> a -> b
$ OAuth2 -> URIRef Absolute
oauth2RedirectUri OAuth2
oa)
             ]