Ejemplo n.º 1
0
        public static async Task <OAuth2Client> RegisterClientAsync(Uri clientRegistrationEndpoint, string initialAccessToken, string softwareId, string softwareVersion, OAuth2TokenEndpointAuthMethod tokenEndpointAuthMethod, List <OAuth2GrantType> grantTypes, List <string> redirectUris, List <string> scopes)
        {
            /* STEP 1: validate all arguments */

            // validate our clientRegistrationEndpoint uri
            if (clientRegistrationEndpoint == null)
            {
                throw new ArgumentNullException(nameof(clientRegistrationEndpoint));
            }
            else if (clientRegistrationEndpoint.Scheme.ToLowerInvariant() != "https")
            {
                throw new ArgumentException("clientRegistrationEndpoint must be secured with the https protocol.", nameof(clientRegistrationEndpoint));
            }

            /* validate redirectUris: make sure that the redirectUris use HTTPS or a non-HTTP protocol (e.g. an app-specific protocol on a mobile
             * device).  HTTP is also valid with redirectUris--but only for the localhost. */
            if (redirectUris != null)
            {
                for (int iRedirectUri = 0; iRedirectUri < redirectUris.Count; iRedirectUri++)
                {
                    // break the redirectUri down into its components (scheme, host, etc.)
                    Uri uri;
                    try
                    {
                        uri = new Uri(redirectUris[iRedirectUri]);
                    }
                    catch
                    {
                        throw new ArgumentException("redirectUris[" + iRedirectUri + "] is a malformed redirect uri.", nameof(redirectUris));
                    }
                    //
                    if (uri.Host == null)
                    {
                        throw new ArgumentException("redirectUris[" + iRedirectUri + "] is a malformed redirect uri.", nameof(redirectUris));
                    }
                    //
                    switch (uri.Scheme.ToLowerInvariant())
                    {
                    case "http":
                        // HTTP scheme is okay as long as the server is localhost (i.e. communication is on the loopback interface).
                        if (uri.Host != "127.0.0.1" && uri.Host != "::1" && uri.Host.ToLowerInvariant() != "localhost")
                        {
                            throw new ArgumentException("redirectUris[" + iRedirectUri + "] must be secured with the https protocol.", nameof(redirectUris));
                        }
                        break;

                    case "https":
                        // HTTPS scheme is okay
                        break;

                    default:
                        // custom app-specific schemes are okay
                        break;
                    }
                }
            }

            // validate our scopes: they cannot contain any spaces and they must conform to our "allowable characters" rules.
            if (scopes != null)
            {
                for (int iScope = 0; iScope < scopes.Count; iScope++)
                {
                    if (ContainsOnlyAllowedScopeCharacters(scopes[iScope]) == false)
                    {
                        throw new ArgumentException("scopes[" + iScope + "] may only contain letters, numbers and the following characters: '" + string.Join("', '", _allowedScopeSpecialCharactersAsString.ToArray()) + "'", nameof(scopes));
                    }
                }
            }

            // validate the grantTypes

            /* Supported grantType options (including combinations) are:
             * .AuthorizationCode
             * .AuthorizationCode, .RefreshToken
             * .Implicit
             * .Password
             * .Password, .RefreshToken
             * .ClientCredentials */
            OAuth2GrantType grantType;
            bool            requestRefreshTokenGrantType = false;

            if (grantTypes == null || grantTypes.Count == 0)
            {
                // if no grant type was provided, use the default
                grantType = OAuth2GrantType.AuthorizationCode;
            }
            else if (grantTypes.Count == 1 && grantTypes.Contains(OAuth2GrantType.AuthorizationCode))
            {
                grantType = OAuth2GrantType.AuthorizationCode;
            }
            else if (grantTypes.Count == 2 && grantTypes.Contains(OAuth2GrantType.AuthorizationCode) && grantTypes.Contains(OAuth2GrantType.RefreshToken))
            {
                grantType = OAuth2GrantType.AuthorizationCode;
                requestRefreshTokenGrantType = true;
            }
            else if (grantTypes.Count == 1 && grantTypes.Contains(OAuth2GrantType.Implicit))
            {
                grantType = OAuth2GrantType.Implicit;
            }
            else if (grantTypes.Count == 1 && grantTypes.Contains(OAuth2GrantType.Password))
            {
                grantType = OAuth2GrantType.Password;
            }
            else if (grantTypes.Count == 2 && grantTypes.Contains(OAuth2GrantType.Password) && grantTypes.Contains(OAuth2GrantType.RefreshToken))
            {
                grantType = OAuth2GrantType.Password;
                requestRefreshTokenGrantType = true;
            }
            else if (grantTypes.Count == 1 && grantTypes.Contains(OAuth2GrantType.ClientCredentials))
            {
                grantType = OAuth2GrantType.ClientCredentials;
            }
            else
            {
                throw new ArgumentException("grantTypes contains an " + (grantTypes.Count > 1 ? "invalid combination of grant types" : "invalid grant type") + ".", nameof(grantTypes));
            }

            // validate the grantType and tokenEndpointAuthMethod (and assign the corresponding responseType)
            OAuth2ResponseType?responseType = null;

            switch (grantType)
            {
            case OAuth2GrantType.AuthorizationCode:
                responseType = OAuth2ResponseType.Code;
                if (tokenEndpointAuthMethod != OAuth2TokenEndpointAuthMethod.ClientSecretBasic && tokenEndpointAuthMethod != OAuth2TokenEndpointAuthMethod.None)
                {
                    throw new ArgumentException("A grantType does not support the requested tokenEndpointAuthMethod.", nameof(grantType));
                }
                break;

            case OAuth2GrantType.Implicit:
                responseType = OAuth2ResponseType.Token;
                if (tokenEndpointAuthMethod != OAuth2TokenEndpointAuthMethod.None)
                {
                    throw new ArgumentException("A grantType does not support the requested tokenEndpointAuthMethod.", nameof(grantType));
                }
                break;

            case OAuth2GrantType.ClientCredentials:
                responseType = null;
                if (tokenEndpointAuthMethod != OAuth2TokenEndpointAuthMethod.ClientSecretBasic)
                {
                    throw new ArgumentException("A grantType does not support the requested tokenEndpointAuthMethod.", nameof(grantType));
                }
                break;

            case OAuth2GrantType.Password:
                responseType = null;
                if (tokenEndpointAuthMethod != OAuth2TokenEndpointAuthMethod.ClientSecretBasic)
                {
                    throw new ArgumentException("A grantType does not support the requested tokenEndpointAuthMethod.", nameof(grantType));
                }
                break;
            }

            // verify that, if grant types require redirect uris, our caller has included at least one redirectUri.
            switch (grantType)
            {
            case OAuth2GrantType.AuthorizationCode:
            case OAuth2GrantType.Implicit:
                if (redirectUris == null || redirectUris.Count == 0)
                {
                    throw new ArgumentException(grantType.ToString() + " requires redirectUris.", nameof(redirectUris));
                }
                break;

            default:
                // no response URIs required.
                break;
            }

            /* STEP 2: create our JSON request payload */
            RegisterClientRequest requestPayload = new RegisterClientRequest();

            requestPayload.software_id      = softwareId;
            requestPayload.software_version = softwareVersion;
            if (redirectUris != null)
            {
                requestPayload.redirect_uris = redirectUris.ToArray();
            }
            // token endpoint auth method
            requestPayload.token_endpoint_auth_method = OAuth2Convert.ConvertTokenEndpointAuthMethodToString(tokenEndpointAuthMethod);
            // grant types
            if (requestRefreshTokenGrantType)
            {
                requestPayload.grant_types = new string[] { OAuth2Convert.ConvertGrantTypeToString(grantType), OAuth2Convert.ConvertGrantTypeToString(OAuth2GrantType.RefreshToken) };
            }
            else
            {
                requestPayload.grant_types = new string[] { OAuth2Convert.ConvertGrantTypeToString(grantType) };
            }
            // response types
            if (responseType != null)
            {
                requestPayload.response_types = new string[] { OAuth2Convert.ConvertResponseTypeToString(responseType.Value) };
            }
            // scopes
            if (scopes != null)
            {
                requestPayload.scope = String.Join(" ", scopes.ToArray());
            }
            //
            string jsonEncodedRequestPayload = JsonConvert.SerializeObject(requestPayload, Formatting.None,
                                                                           new JsonSerializerSettings()
            {
                NullValueHandling = NullValueHandling.Ignore
            });

            /* STEP 3: send our dynamic client registration request */
            try
            {
                using (HttpClient httpClient = new HttpClient())
                {
                    // create request
                    var requestMessage = new HttpRequestMessage(HttpMethod.Post, clientRegistrationEndpoint);
                    requestMessage.Content = new HttpStringContent(jsonEncodedRequestPayload, Windows.Storage.Streams.UnicodeEncoding.Utf8, "application/json");
                    requestMessage.Headers.Accept.Clear();
                    requestMessage.Headers.Accept.Add(new Windows.Web.Http.Headers.HttpMediaTypeWithQualityHeaderValue("application/json"));
                    if (initialAccessToken != null)
                    {
                        requestMessage.Headers.Authorization = new Windows.Web.Http.Headers.HttpCredentialsHeaderValue("Bearer", initialAccessToken);
                    }
                    // send request
                    HttpResponseMessage responseMessage = await httpClient.SendRequestAsync(requestMessage);

                    // process response
                    switch (responseMessage.StatusCode)
                    {
                    case HttpStatusCode.Created:
                    {
                        // client was registered; parse response
                        RegisterClientResponse responsePayload = JsonConvert.DeserializeObject <RegisterClientResponse>(await responseMessage.Content.ReadAsStringAsync());
                        if (responsePayload.client_id == null)
                        {
                            throw new OAuth2ServerErrorException();
                        }
                        OAuth2Client client = new OAuth2Client();
                        // Id
                        client.Id = responsePayload.client_id;
                        // Secret
                        client.Secret = responsePayload.client_secret;
                        // IssuedAt
                        if (responsePayload.client_id_issued_at != null)
                        {
                            client.IssuedAt = DateTimeOffset.FromUnixTimeSeconds(long.Parse(responsePayload.client_id_issued_at.Value.ToString()));
                        }
                        // ExpiresAt
                        if (responsePayload.client_secret_expires_at != null && responsePayload.client_secret_expires_at != 0)
                        {
                            client.SecretExpiresAt = DateTimeOffset.FromUnixTimeSeconds(long.Parse(responsePayload.client_secret_expires_at.Value.ToString()));
                        }
                        // SoftwareId
                        client.SoftwareId = responsePayload.software_id;
                        // SoftwareVersion
                        client.SoftwareVersion = responsePayload.software_version;
                        // RedirectUris
                        if (responsePayload.redirect_uris != null)
                        {
                            client.RedirectUris = responsePayload.redirect_uris.ToList <string>();
                        }
                        // TokenEndpointAuthMethod
                        if (responsePayload.token_endpoint_auth_method != null)
                        {
                            OAuth2TokenEndpointAuthMethod?allowedTokenEndpointAuthMethod = OAuth2Convert.ConvertStringToTokenEndpointAuthMethod(responsePayload.token_endpoint_auth_method);
                            if (allowedTokenEndpointAuthMethod != null)
                            {
                                client.TokenEndpointAuthMethod = allowedTokenEndpointAuthMethod.Value;
                            }
                        }
                        // GrantTypes
                        if (responsePayload.grant_types != null)
                        {
                            client.GrantTypes = new List <OAuth2.OAuth2GrantType>();
                            foreach (string grantTypeAsString in responsePayload.grant_types)
                            {
                                OAuth2GrantType?allowedGrantType = OAuth2Convert.ConvertStringToGrantType(grantTypeAsString);
                                if (allowedGrantType != null)
                                {
                                    client.GrantTypes.Add(allowedGrantType.Value);
                                }
                            }
                        }
                        // Scope
                        client.Scope = responsePayload.scope;
                        // RegistrationAccessToken and RegistrationAccessUri
                        if (responsePayload.registration_access_token != null && responsePayload.registration_client_uri != null)
                        {
                            // RegistrationAccessToken
                            client.RegistrationToken = responsePayload.registration_access_token;
                            // RegistrationAccessUri
                            client.RegistrationUri = responsePayload.registration_client_uri;
                        }
                        // return our client
                        return(client);
                    }

                    case HttpStatusCode.BadRequest:
                    {
                        // process the "bad request" response
                        OAuth2ErrorResponse responsePayload = JsonConvert.DeserializeObject <OAuth2ErrorResponse>(await responseMessage.Content.ReadAsStringAsync());
                        if (responsePayload.error == null)
                        {
                            throw new OAuth2ServerErrorException();
                        }

                        switch (responsePayload.error.ToLowerInvariant())
                        {
                        case "invalid_redirect_uri":
                            throw new ArgumentException(responsePayload.error_description ?? responsePayload.error, nameof(redirectUris));

                        case "invalid_client_metadata":
                            throw new ArgumentException(responsePayload.error_description ?? responsePayload.error);

                        default:
                            throw new OAuth2HttpException(responseMessage.StatusCode);
                        }
                    }

                    case HttpStatusCode.PaymentRequired:
                        throw new OAuth2PaymentRequiredException();

                    case HttpStatusCode.Forbidden:
                        throw new OAuth2ForbiddenException();

                    case HttpStatusCode.TooManyRequests:
                        throw new OAuth2TooManyRequestsException();

                    case HttpStatusCode.InternalServerError:
                        throw new OAuth2ServerErrorException();

                    case HttpStatusCode.ServiceUnavailable:
                        throw new OAuth2ServiceUnavailableException();

                    default:
                        throw new OAuth2HttpException(responseMessage.StatusCode);
                    }
                }
            }
            catch (JsonException)
            {
                // JSON parsing error; this is catastrophic.
                throw new OAuth2ServerErrorException();
            }
            catch
            {
                // NOTE: callers must catch non-HTTP networking exceptions
                throw;
            }
        }
Ejemplo n.º 2
0
        public async Task <OAuth2Token> RequestTokenAsync(Uri tokenEndpoint, string authorizationCode, string redirectUri)
        {
            /* STEP 1: validate all arguments */

            // validate our tokenEndpoint uri
            if (tokenEndpoint == null)
            {
                throw new ArgumentNullException(nameof(tokenEndpoint));
            }
            else if (tokenEndpoint.Scheme.ToLowerInvariant() != "https")
            {
                throw new ArgumentException("tokenEndpoint must be secured with the https protocol.", nameof(tokenEndpoint));
            }

            if (authorizationCode == null)
            {
                throw new ArgumentNullException(nameof(authorizationCode));
            }

            /* validate redirectUri: make sure that the redirectUri uses HTTPS or a non-HTTP protocol (e.g. an app-specific protocol on a mobile
             * device).  HTTP is also valid with a redirectUri--but only for the localhost. */
            if (redirectUri != null)
            {
                // break the redirectUri down into its components (scheme, host, etc.)
                Uri uri;
                try
                {
                    uri = new Uri(redirectUri);
                }
                catch
                {
                    throw new ArgumentException("redirectUri is a malformed redirect uri.", nameof(redirectUri));
                }
                //
                if (uri.Host == null)
                {
                    throw new ArgumentException("redirectUri is a malformed redirect uri.", nameof(redirectUri));
                }
                //
                switch (uri.Scheme.ToLowerInvariant())
                {
                case "http":
                    // HTTP scheme is okay as long as the server is localhost (i.e. communication is on the loopback interface).
                    if (uri.Host != "127.0.0.1" && uri.Host != "::1" && uri.Host.ToLowerInvariant() != "localhost")
                    {
                        throw new ArgumentException("redirectUri must be secured with the https protocol.", nameof(redirectUri));
                    }
                    break;

                case "https":
                    // HTTPS scheme is okay
                    break;

                default:
                    // custom app-specific schemes are okay
                    break;
                }
            }

            /* STEP 2: create our WwwFormUrlencoded request payload */
            Dictionary <string, string> formParameters = new Dictionary <string, string>();

            formParameters["grant_type"] = OAuth2Convert.ConvertGrantTypeToString(OAuth2GrantType.AuthorizationCode);
            formParameters["code"]       = authorizationCode;
            if (redirectUri != null)
            {
                formParameters["redirect_uri"] = redirectUri;
            }
            // if we have a client secret, we will authenticate via HTTP BASIC auth; otherwise, pass in our client_id as a form paramter
            if (this.Secret == null)
            {
                formParameters["client_id"] = this.Id;
            }

            /* STEP 3: send our access token request */
            try
            {
                using (HttpClient httpClient = new HttpClient())
                {
                    // create request
                    var requestMessage = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint);
                    requestMessage.Content = new HttpFormUrlEncodedContent(formParameters);
                    requestMessage.Headers.Accept.Clear();
                    requestMessage.Headers.Accept.Add(new Windows.Web.Http.Headers.HttpMediaTypeWithQualityHeaderValue("application/json"));
                    // if we have a client secret, add our client_id and client_secret to authenticate via HTTP Basic authentication
                    if (this.Secret != null)
                    {
                        string authorizationCredentialString = this.Id + ":" + this.Secret;
                        requestMessage.Headers.Authorization = new Windows.Web.Http.Headers.HttpCredentialsHeaderValue("Basic", Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(authorizationCredentialString)));
                    }
                    // send request
                    HttpResponseMessage responseMessage = await httpClient.SendRequestAsync(requestMessage);

                    // process response
                    switch (responseMessage.StatusCode)
                    {
                    case HttpStatusCode.Ok:
                    {
                        // token was requested; parse response
                        RequestTokenResponse responsePayload = JsonConvert.DeserializeObject <RequestTokenResponse>(await responseMessage.Content.ReadAsStringAsync());
                        if (responsePayload.token_type.ToLowerInvariant() != "bearer")
                        {
                            throw new OAuth2ServerErrorException();
                        }
                        OAuth2Token token = new OAuth2Token()
                        {
                            Id = responsePayload.access_token
                        };
                        if (responsePayload.expires_in != null)
                        {
                            token.ExpiresAt = DateTimeOffset.UtcNow.AddSeconds(long.Parse(responsePayload.expires_in.Value.ToString()));
                        }
                        //if (responsePayload.refresh_token != null)
                        //{
                        //    token.RefreshTokenId = responsePayload.refresh_token;
                        //}
                        //if (responsePayload.scope != null)
                        //{
                        //    token.Scope = responsePayload.scope;
                        //}
                        return(token);
                    }

                    case HttpStatusCode.BadRequest:
                    {
                        // process the "bad request" response
                        OAuth2ErrorResponse responsePayload = JsonConvert.DeserializeObject <OAuth2ErrorResponse>(await responseMessage.Content.ReadAsStringAsync());
                        if (responsePayload.error == null)
                        {
                            throw new OAuth2ServerErrorException();
                        }

                        switch (responsePayload.error.ToLowerInvariant())
                        {
                        case "invalid_request":
                            throw new ArgumentException(responsePayload.error_description ?? responsePayload.error);

                        case "invalid_client":
                            throw new OAuth2InvalidClientException(responsePayload.error_description ?? responsePayload.error);

                        case "invalid_grant":
                            throw new ArgumentException(responsePayload.error_description ?? responsePayload.error, nameof(authorizationCode));

                        case "unauthorized_client":
                            throw new OAuth2UnauthorizedClientException(responsePayload.error_description ?? responsePayload.error);

                        //case "unsupported_grant_type":
                        //    // NOTE: this error should never occur when requesting a token via authorization_code
                        //case "invalid_scope":
                        //    // NOTE: this error should never occur when requesting a token via authorization_code
                        default:
                            throw new OAuth2HttpException(responseMessage.StatusCode);
                        }
                    }

                    case HttpStatusCode.TooManyRequests:
                        throw new OAuth2TooManyRequestsException();

                    case HttpStatusCode.InternalServerError:
                        throw new OAuth2ServerErrorException();

                    case HttpStatusCode.ServiceUnavailable:
                        throw new OAuth2ServiceUnavailableException();

                    default:
                        throw new OAuth2HttpException(responseMessage.StatusCode);
                    }
                }
            }
            catch (JsonException)
            {
                // JSON parsing error; this is catastrophic.
                throw new OAuth2ServerErrorException();
            }
            catch
            {
                // NOTE: callers must catch non-HTTP networking exceptions
                throw;
            }
        }