nuts-foundation/nuts-node

View on GitHub
auth/oauth/types.go

Summary

Maintainability
A
0 mins
Test Coverage
A
94%
/*
 * Nuts node
 * Copyright (C) 2023 Nuts community
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

// Package oauth contains generic OAuth related functionality, variables and constants
package oauth

import (
    "encoding/json"
    "net/url"

    "github.com/nuts-foundation/nuts-node/core"
)

// this file contains constants, variables and helper functions for OAuth related code

// TokenResponse is the OAuth access token response.
// Through With() and Get() additional parameters (for OpenID4VCI, for instance) can be set and retrieved.
type TokenResponse struct {
    AccessToken string  `json:"access_token"`
    ExpiresIn   *int    `json:"expires_in,omitempty"`
    TokenType   string  `json:"token_type"`
    Scope       *string `json:"scope,omitempty"`

    additionalParams map[string]interface{}
}

var _ json.Unmarshaler = (*TokenResponse)(nil)
var _ json.Marshaler = (*TokenResponse)(nil)

func (t *TokenResponse) UnmarshalJSON(data []byte) error {
    type Alias TokenResponse
    var result Alias
    // base parameters
    if err := json.Unmarshal(data, &result); err != nil {
        return err
    }
    // extension parameters
    additionalParams := map[string]interface{}{}
    _ = json.Unmarshal(data, &additionalParams) // can't fail, already unmarshalled
    delete(additionalParams, "access_token")
    delete(additionalParams, "expires_in")
    delete(additionalParams, "token_type")
    delete(additionalParams, "scope")
    *t = TokenResponse(result)
    if len(additionalParams) > 0 {
        t.additionalParams = additionalParams
    }
    return nil
}

func (t TokenResponse) MarshalJSON() ([]byte, error) {
    result := make(map[string]interface{})
    for key, value := range t.additionalParams {
        result[key] = value
    }
    result["access_token"] = t.AccessToken
    result["expires_in"] = t.ExpiresIn
    result["token_type"] = t.TokenType
    result["scope"] = t.Scope

    return json.Marshal(result)
}

// With adds a parameter to the token response.
// It's a builder-style function.
// It should not be used to set any of the base parameters (access_token, expires_in, token_type, scope).
func (t *TokenResponse) With(key string, value interface{}) *TokenResponse {
    if t.additionalParams == nil {
        t.additionalParams = make(map[string]interface{})
    }
    t.additionalParams[key] = value
    return t
}

// Get returns the value of the additional parameter with the given key as a string.
// If the key does not exist or the value is not a string, it returns an empty string.
// It should not be used to get any of the base parameters (access_token, expires_in, token_type, scope).
func (t TokenResponse) Get(key string) string {
    if t.additionalParams == nil {
        return ""
    }
    if val, ok := t.additionalParams[key]; ok {
        if str, ok := val.(string); ok {
            return str
        }
    }
    return ""
}

const (
    // AccessTokenRequestStatusPending is the status for a pending access token
    AccessTokenRequestStatusPending = "pending"
    // AccessTokenRequestStatusActive is the status for an active access token
    AccessTokenRequestStatusActive = "active"
)

// metadata endpoints
const (
    // AuthzServerWellKnown is the well-known base path for the oauth authorization server metadata as defined in RFC8414
    AuthzServerWellKnown = "/.well-known/oauth-authorization-server"
    // ClientMetadataPath is the path to the client metadata relative to the complete did:web URL
    ClientMetadataPath = "/oauth-client"
    // OpenIdCredIssuerWellKnown is the well-known base path for the openID credential issuer metadata as defined in
    // OpenID4VCI specification
    OpenIdCredIssuerWellKnown = "/.well-known/openid-credential-issuer"
)

// oauth parameter keys
const (
    // AssertionParam is the parameter name for the assertion parameter. (RFC021)
    AssertionParam = "assertion"
    // AuthorizationDetailsParam is the parameter name for the authorization_details parameter. (RFC9396)
    AuthorizationDetailsParam = "authorization_details"
    // ClientIDParam is the parameter name for the client_id parameter. (RFC6749)
    ClientIDParam = "client_id"
    // ClientIDSchemeParam is the parameter name for the client_id_scheme parameter. (OpenID4VP)
    ClientIDSchemeParam = "client_id_scheme"
    // ClientMetadataParam is the parameter name for the client_metadata parameter. (OpenID4VP)
    ClientMetadataParam = "client_metadata"
    // ClientMetadataURIParam is the parameter name for the client_metadata_uri parameter. (OpenID4VP)
    ClientMetadataURIParam = "client_metadata_uri"
    // CNonceParam is the parameter name for the c_nonce parameter. (OpenID4VCI)
    CNonceParam = "c_nonce"
    // CodeParam is the parameter name for the code parameter. (RFC6749)
    CodeParam = CodeResponseType
    // CodeChallengeParam is the parameter name for the code_challenge parameter. (RFC7636)
    CodeChallengeParam = "code_challenge"
    // CodeChallengeMethodParam is the parameter name for the code_challenge_method parameter. (RFC7636)
    CodeChallengeMethodParam = "code_challenge_method"
    // CodeVerifierParam is the parameter name for the code_verifier parameter. (RFC7636)
    CodeVerifierParam = "code_verifier"
    // GrantTypeParam is the parameter name for the grant_type parameter. (RFC6749)
    GrantTypeParam = "grant_type"
    // NonceParam is the parameter name for the nonce parameter
    NonceParam = "nonce"
    // PresentationDefParam is the parameter name for the OpenID4VP presentation_definition parameter. (OpenID4VP)
    PresentationDefParam = "presentation_definition"
    // PresentationDefUriParam is the parameter name for the OpenID4VP presentation_definition_uri parameter. (OpenID4VP)
    PresentationDefUriParam = "presentation_definition_uri"
    // PresentationSubmissionParam is the parameter name for the presentation_submission parameter. (OpenID4VP)
    PresentationSubmissionParam = "presentation_submission"
    // RedirectURIParam is the parameter name for the redirect_uri parameter. (RFC6749)
    RedirectURIParam = "redirect_uri"
    // RequestParam is the parameter name for the request parameter.    (RFC9101)
    RequestParam = "request"
    // RequestURIParam is the parameter name for the request parameter. (RFC9101)
    RequestURIParam = "request_uri"
    // RequestURIMethodParam states what http method (get/post) should be used for RequestURIParam. (OpenID4VP)
    RequestURIMethodParam = "request_uri_method"
    // ResponseModeParam is the parameter name for the OAuth2 response_mode parameter.
    ResponseModeParam = "response_mode"
    // ResponseTypeParam is the parameter name for the response_type parameter. (RFC6749)
    ResponseTypeParam = "response_type"
    // ResponseURIParam is the parameter name for the OpenID4VP response_uri parameter.
    ResponseURIParam = "response_uri"
    // ScopeParam is the parameter name for the scope parameter. (RFC6749)
    ScopeParam = "scope"
    // StateParam is the parameter name for the state parameter. (RFC6749)
    StateParam = "state"
    // VpTokenParam is the parameter name for the vp_token parameter. (OpenID4VP)
    VpTokenParam = "vp_token"
    // WalletMetadataParam is used by the wallet to provide its metadata in an authorization request when RequestURIMethodParam is 'post'
    WalletMetadataParam = "wallet_metadata"
    // WalletNonceParam is a wallet generated nonce to prevent authorization request replay when RequestURIMethodParam is 'post'
    WalletNonceParam = "wallet_nonce"
)

// grant types
const (
    // AuthorizationCodeGrantType is the grant_type for the authorization_code grant type. (RFC6749)
    AuthorizationCodeGrantType = "authorization_code"
    // PreAuthorizedCodeGrantType is the grant_type for the pre-authorized_code grant type. (OpenID4VCI)
    PreAuthorizedCodeGrantType = "urn:ietf:params:oauth:grant-type:pre-authorized_code"
    // VpTokenGrantType is the grant_type for the vp_token-bearer grant type. (RFC021)
    VpTokenGrantType = "vp_token-bearer"
)

// response types
const (
    // CodeResponseType is the parameter name for the code parameter. (RFC6749)
    CodeResponseType = "code"
    // VPTokenResponseType is paramter name for the vp_token repsponse type. (OpenID4VP)
    VPTokenResponseType = "vp_token"
)

const (
    // ErrorParam is the parameter name for the error parameter
    ErrorParam = "error"
    // ErrorDescriptionParam is the parameter name for the error_description parameter
    ErrorDescriptionParam = "error_description"
)

// IssuerIdToWellKnown converts the OAuth2 Issuer identity to the specified well-known endpoint by inserting the well-known at the root of the path.
// It returns no url and an error when issuer is not a valid URL.
func IssuerIdToWellKnown(issuer string, wellKnown string, strictmode bool) (*url.URL, error) {
    issuerURL, err := core.ParsePublicURL(issuer, strictmode)
    if err != nil {
        return nil, err
    }
    return issuerURL.Parse(wellKnown + issuerURL.EscapedPath())
}

// AuthorizationServerMetadata defines the OAuth Authorization Server metadata.
// Specified by https://www.rfc-editor.org/rfc/rfc8414.txt
type AuthorizationServerMetadata struct {
    // Issuer defines the authorization server's identifier, which is a URL that uses the "https" scheme and has no query or fragment components.
    Issuer string `json:"issuer,omitempty"`

    /* ******** /authorize ******** */

    // AuthorizationEndpoint defines the URL of the authorization server's authorization endpoint [RFC6749]
    AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`

    // ResponseTypesSupported defines what response types a client can request
    ResponseTypesSupported []string `json:"response_types_supported,omitempty"`

    // ResponseModesSupported defines what response modes a client can request
    // Currently supports
    // - query for response_type=code
    // - direct_post for response_type=["vp_token", "vp_token id_token"]
    // TODO: is `form_post` something we want in the future?
    ResponseModesSupported []string `json:"response_modes_supported,omitempty"`

    /* ******** /token ******** */

    // TokenEndpoint defines the URL of the authorization server's token endpoint [RFC6749].
    TokenEndpoint string `json:"token_endpoint,omitempty"`

    // GrantTypesSupported is a list of the OAuth 2.0 grant type values that this authorization server supports.
    GrantTypesSupported []string `json:"grant_types_supported,omitempty"`

    //// TODO: what do we support?
    //// TokenEndpointAuthMethodsSupported is a JSON array containing a list of client authentication methods supported by this token endpoint.
    //// Client authentication method values are used in the "token_endpoint_auth_method" parameter defined in Section 2 of [RFC7591].
    //// If omitted, the default is "client_secret_basic" -- the HTTP Basic Authentication Scheme specified in Section 2.3.1 of OAuth 2.0 [RFC6749].
    //TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
    //
    //// TODO: May be needed depending on TokenEndpointAuthMethodsSupported
    //// TokenEndpointAuthSigningAlgValuesSupported is a JSON array containing a list of the JWS signing algorithms ("alg" values) supported by the token endpoint
    //// for the signature on the JWT [JWT] used to authenticate the client at the token endpoint for the "private_key_jwt" and "client_secret_jwt" authentication methods.
    //// This metadata entry MUST be present if either of these authentication methods are specified in the "token_endpoint_auth_methods_supported" entry.
    //// No default algorithms are implied if this entry is omitted. Servers SHOULD support "RS256". The value "none" MUST NOT be used.
    //TokenEndpointAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported,omitempty"`

    /* ******** openid4vc ******** */

    // PreAuthorizedGrantAnonymousAccessSupported indicates whether anonymous access (requests without client_id) for pre-authorized code grant flows.
    // See https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-oauth-20-authorization-serv
    PreAuthorizedGrantAnonymousAccessSupported bool `json:"pre-authorized_grant_anonymous_access_supported,omitempty"`

    // PresentationDefinitionEndpoint defines the URL of the authorization server's presentation definition endpoint.
    // See https://nuts-foundation.gitbook.io/drafts/rfc/rfc021-vp_token-grant-type
    PresentationDefinitionEndpoint string `json:"presentation_definition_endpoint,omitempty"`

    // PresentationDefinitionUriSupported specifies whether the Wallet supports the transfer of presentation_definition by reference, with true indicating support.
    // If omitted, the default value is true. (hence pointer, or add custom unmarshalling)
    PresentationDefinitionUriSupported *bool `json:"presentation_definition_uri_supported,omitempty"`

    // VPFormatsSupported is an object containing a list of key value pairs, where the key is a string identifying a Credential format supported by the Wallet.
    VPFormatsSupported map[string]map[string][]string `json:"vp_formats_supported,omitempty"`

    // VPFormats is an object containing a list of key value pairs, where the key is a string identifying a Credential format supported by the Verifier.
    // TODO: Remove. VPFormatsSupported is the correct param, but the OpenID4VP spec is ambiguous so support both for now.
    VPFormats map[string]map[string][]string `json:"vp_formats,omitempty"`

    // ClientIdSchemesSupported defines the `client_id_schemes` currently supported.
    // If omitted, the default value is `pre-registered` (referring to the client), which is currently not supported.
    ClientIdSchemesSupported []string `json:"client_id_schemes_supported,omitempty"`

    // DPoPSigningAlgValuesSupported is a JSON array containing a list of the DPoP proof JWS signing algorithms ("alg" values) supported by the token endpoint.
    DPoPSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported,omitempty"`

    /* ******** JWT-Secured Authorization Request RFC9101 & OpenID Connect Core v1.0: ยง6. Passing Request Parameters as JWTs ******** */

    // RequireSignedRequestObject specifies if the authorization server requires the use of signed request objects.
    RequireSignedRequestObject bool `json:"require_signed_request_object,omitempty"`

    // RequestObjectSigningAlgValuesSupported is a JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for Request Objects, which are described in Section 6.1 of OpenID Connect Core 1.0 [OpenID.Core].
    // These algorithms are used both when the Request Object is passed by value (using the request parameter) and when it is passed by reference (using the request_uri parameter).
    RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported,omitempty"`
}

// OAuthClientMetadata defines the OAuth Client metadata.
// Specified by https://www.rfc-editor.org/rfc/rfc7591.html and elsewhere.
type OAuthClientMetadata struct {
    // RedirectURIs lists all URIs that the client may use in any redirect-based flow.
    // From https://www.rfc-editor.org/rfc/rfc7591.html
    RedirectURIs []string `json:"redirect_uris,omitempty"`

    // TODO: What do we use? Must provide a value if its not "client_secret_basic"
    // TokenEndpointAuthMethod indicator of the requested authentication method for the token endpoint.
    // If unspecified or omitted, the default is "client_secret_basic", denoting the HTTP Basic authentication scheme as specified in Section 2.3.1 of OAuth 2.0.
    // Examples are: none, client_secret_post, client_secret_basic, tls_client_auth.
    // From https://www.rfc-editor.org/rfc/rfc7591.html
    // TODO: Can "tls_client_auth" replace /n2n/ for pre-authorized_code flow? https://www.rfc-editor.org/rfc/rfc8705.html
    TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`

    // GrantTypes lists all supported grant_types. Defaults to "authorization_code" if omitted.
    // From https://www.rfc-editor.org/rfc/rfc7591.html
    GrantTypes []string `json:"grant_types,omitempty"`

    // ResponseTypes lists all supported response_types. Defaults to "code". Must contain the values corresponding to listed GrantTypes.
    // From https://www.rfc-editor.org/rfc/rfc7591.html
    ResponseTypes []string `json:"response_types,omitempty"`

    // Scope contains a space-separated list of scopes the client can request.
    // From https://www.rfc-editor.org/rfc/rfc7591.html
    // TODO: I don't see the use for this. The idea is that an AS does not assign scopes to a client that it does not support (or wants to request at any time), but seems like unnecessary complexity for minimal safety.
    Scope string `json:"scope,omitempty"`

    // Contacts contains an array of strings representing ways to contact people responsible for this client, typically email addresses.
    // From https://www.rfc-editor.org/rfc/rfc7591.html
    // TODO: remove? Can plug DID docs contact info.
    Contacts []string `json:"contacts,omitempty"`

    // JwksURI URL string referencing the client's JSON Web Key (JWK) Set [RFC7517] document, which contains the client's public keys.
    // From https://www.rfc-editor.org/rfc/rfc7591.html
    // TODO: remove? Can list the DID's keys. Could be useful if authorization without DIDs/VCs is needed.
    // TODO: In EBSI it is a required field for the Service Wallet Metadata https://api-conformance.ebsi.eu/docs/ct/providers-and-wallets-metadata#service-wallet-metadata
    JwksURI string `json:"jwks_uri,omitempty"`
    // Jwks includes the JWK Set of a client. Mutually exclusive with JwksURI.
    // From https://www.rfc-editor.org/rfc/rfc7591.html
    Jwks any `json:"jwks,omitempty"`

    // SoftwareID is a unique identifier string (e.g., a Universally Unique Identifier (UUID)) assigned by the client developer.
    // From https://www.rfc-editor.org/rfc/rfc7591.html
    SoftwareID string `json:"software_id,omitempty"`
    // SoftwareVersion is a version identifier string for the client software identified by "software_id".
    // From https://www.rfc-editor.org/rfc/rfc7591.html
    // TODO: Including a software_id + software_version could provide us with some upgrade paths in the future.
    SoftwareVersion string `json:"software_version,omitempty"`

    // TODO: ignored values: client_name, client_uri, logo_uri, tos_uri, policy_uri.
    // TODO: Things like client_name and logo may enhance the user experience when asking to accept authorization requests, but this should probably be added on the server size for that?

    /*********** OpenID4VCI ***********/

    // CredentialOfferEndpoint contains a URL where the pre-authorized_code flow offers a credential.
    // https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-client-metadata
    // TODO: openid4vci duplicate. Also defined on /.well-known/openid-credential-wallet to be /n2n/identity/{did}/openid4vci/credential_offer
    CredentialOfferEndpoint string `json:"credential_offer_endpoint,omitempty"`

    /*********** OpenID4VP ***********/
    // VPFormats lists the vp_formats supported by the client. See additional comments on vpFormatsSupported.
    // https://openid.bitbucket.io/connect/openid-4-verifiable-presentations-1_0.html#name-verifier-metadata-client-me
    VPFormats map[string]map[string][]string `json:"vp_formats,omitempty"`

    // ClientIdScheme is a string identifying the Client Identifier scheme. The value range defined by this specification is
    // pre-registered, redirect_uri, entity_id, did. If omitted, the default value is pre-registered.
    // https://openid.bitbucket.io/connect/openid-4-verifiable-presentations-1_0.html#name-verifier-metadata-client-me
    ClientIdScheme string `json:"client_id_scheme,omitempty"`
}

// Redirect is the response from the verifier on the direct_post authorization response.
type Redirect struct {
    // RedirectURI is the URI to redirect the user-agent to.
    RedirectURI string `json:"redirect_uri"`
}

// OpenIDCredentialIssuerMetadata represents the metadata of an OpenID credential issuer
type OpenIDCredentialIssuerMetadata struct {
    // - CredentialIssuer: an url representing the credential issuer
    CredentialIssuer string `json:"credential_issuer"`
    // - CredentialEndpoint: an url representing the credential endpoint
    CredentialEndpoint string `json:"credential_endpoint"`
    // - AuthorizationServers: a slice of urls representing the authorization servers (optional)
    AuthorizationServers []string `json:"authorization_servers,omitempty"`
    // - Display: a slice of maps where each map represents the display information (optional)
    Display []map[string]string `json:"display,omitempty"`
}