Example #1
0
        public async Task RetrieveJWKSAsyncReturnsJWKS()
        {
            _restClient.NextExpectedResponse = _responses["single"];

            var actual = await _jwksService.RetrieveJWKSAsync("http://jwks.com/jwks");

            Assert.AreEqual(1, actual.Keys.Count);
            var key = actual.Keys.First();

            Assert.AreEqual("RSA", key.KeyType);
            Assert.AreEqual("sig", key.Use);
            Assert.AreEqual("ALyIC8vj1tqEIvAvpDMQfgosw13LpBS9Z2lsMmuaLDNJjN_FKIb-HVR2qtMj7AYC0-wYJhGxJpTXJTVRRDz-zLN7uredNxuhVj76vmU1tfvEN0Xq2INYoWeJ3d9fZtkBgKl7Enfkgz858DLAfZuJzDycOzuZXR5r29zXMDstT5F5", key.RSAN);
            Assert.AreEqual("AQAB", key.RSAE);
            Assert.AreEqual("PHPOP-00", key.KeyID);
        }
        internal static async Task <MobileConnectStatus> RequestToken(IAuthenticationService authentication, IJWKeysetService jwks, DiscoveryResponse discoveryResponse, Uri redirectedUrl, string expectedState,
                                                                      string expectedNonce, MobileConnectConfig config, MobileConnectRequestOptions options)
        {
            RequestTokenResponse response;

            if (!IsUsableDiscoveryResponse(discoveryResponse))
            {
                return(MobileConnectStatus.StartDiscovery());
            }

            if (string.IsNullOrEmpty(expectedState))
            {
                return(MobileConnectStatus.Error(ErrorCodes.InvalidArgument, "ExpectedState argument was not supplied, this is needed to prevent Cross-Site Request Forgery", null));
            }

            if (string.IsNullOrEmpty(expectedNonce))
            {
                return(MobileConnectStatus.Error(ErrorCodes.InvalidArgument, "expectedNonce argument was not supplied, this is needed to prevent Replay Attacks", null));
            }

            var actualState = HttpUtils.ExtractQueryValue(redirectedUrl.Query, "state");

            if (expectedState != actualState)
            {
                return(MobileConnectStatus.Error(ErrorCodes.InvalidState, "State values do not match, this could suggest an attempted Cross-Site Request Forgery", null));
            }

            try
            {
                var code            = HttpUtils.ExtractQueryValue(redirectedUrl.Query, "code");
                var clientId        = discoveryResponse.ResponseData.response.client_id ?? config.ClientId;
                var clientSecret    = discoveryResponse.ResponseData.response.client_secret ?? config.ClientSecret;
                var requestTokenUrl = discoveryResponse.OperatorUrls.RequestTokenUrl;
                var issuer          = discoveryResponse.ProviderMetadata.Issuer;

                var tokenTask = authentication.RequestTokenAsync(clientId, clientSecret, requestTokenUrl, config.RedirectUrl, code);
                var jwksTask  = jwks.RetrieveJWKSAsync(discoveryResponse.OperatorUrls.JWKSUrl);

                // execute both tasks in parallel
                await Task.WhenAll(tokenTask, jwksTask).ConfigureAwait(false);

                response = tokenTask.Result;

                var maxSupportedVersion = discoveryResponse.ProviderMetadata?.MobileConnectVersionSupported == null ? "mc_v1.1" : discoveryResponse.ProviderMetadata.MobileConnectVersionSupported.MaxSupportedVersionString;

                return(HandleTokenResponse(authentication, response, clientId, issuer, expectedNonce,
                                           maxSupportedVersion, jwksTask.Result, options));
            }
            catch (MobileConnectInvalidArgumentException e)
            {
                Log.Error(() => $"An invalid argument was passed to RequestToken arg={e.Argument}");
                return(MobileConnectStatus.Error(ErrorCodes.InvalidArgument, string.Format("An argument was found to be invalid during the process. The argument was {0}.", e.Argument), e));
            }
            catch (MobileConnectEndpointHttpException e)
            {
                Log.Error(() => $"A general http error occurred in RequestToken redirectedUrl={redirectedUrl} requestTokenUrl={discoveryResponse.OperatorUrls.RequestTokenUrl} jwksUrl={discoveryResponse.OperatorUrls.JWKSUrl}");
                return(MobileConnectStatus.Error(ErrorCodes.HttpFailure, "An HTTP failure occured while calling the operator token endpoint, the endpoint may be inaccessible", e));
            }
            catch (Exception e)
            {
                Log.Error(() => $"A general error occurred in RequestToken redirectedUrl={redirectedUrl} requestTokenUrl={discoveryResponse.OperatorUrls.RequestTokenUrl} jwksUrl={discoveryResponse.OperatorUrls.JWKSUrl}");
                return(MobileConnectStatus.Error(ErrorCodes.Unknown, "A failure occured while requesting a token", e));
            }
        }
        internal static async Task <MobileConnectStatus> RequestHeadlessAuthentication(IAuthenticationService authentication, IJWKeysetService jwks, IIdentityService identity, DiscoveryResponse discoveryResponse, string encryptedMSISDN,
                                                                                       string state, string nonce, MobileConnectConfig config, MobileConnectRequestOptions options, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (!IsUsableDiscoveryResponse(discoveryResponse))
            {
                return(MobileConnectStatus.StartDiscovery());
            }

            MobileConnectStatus status;

            try
            {
                string                clientId          = discoveryResponse.ResponseData.response.client_id ?? config.ClientId;
                string                clientSecret      = discoveryResponse.ResponseData.response.client_secret;
                string                authorizationUrl  = discoveryResponse.OperatorUrls.AuthorizationUrl;
                string                tokenUrl          = discoveryResponse.OperatorUrls.RequestTokenUrl;
                string                issuer            = discoveryResponse.ProviderMetadata.Issuer;
                SupportedVersions     supportedVersions = discoveryResponse.ProviderMetadata.MobileConnectVersionSupported;
                AuthenticationOptions authOptions       = options?.AuthenticationOptions ?? new AuthenticationOptions();
                authOptions.ClientName = discoveryResponse.ApplicationShortName;

                var jwksTask  = jwks.RetrieveJWKSAsync(discoveryResponse.OperatorUrls.JWKSUrl);
                var tokenTask = authentication.RequestHeadlessAuthentication(clientId, clientSecret, authorizationUrl, tokenUrl, config.RedirectUrl, state, nonce,
                                                                             encryptedMSISDN, supportedVersions, authOptions, cancellationToken);

                // execute both tasks in parallel
                await Task.WhenAll(tokenTask, jwksTask).ConfigureAwait(false);

                RequestTokenResponse response = tokenTask.Result;

                status = HandleTokenResponse(authentication, response, clientId, issuer, nonce,
                                             discoveryResponse.ProviderMetadata.MobileConnectVersionSupported.MaxSupportedVersionString, jwksTask.Result, options);
            }
            catch (MobileConnectInvalidArgumentException e)
            {
                Log.Error(() => $"An invalid argument was passed to RequestHeadlessAuthentication arg={e.Argument}");
                return(MobileConnectStatus.Error(ErrorCodes.InvalidArgument, string.Format("An argument was found to be invalid during the process. The argument was {0}.", e.Argument), e));
            }
            catch (MobileConnectEndpointHttpException e)
            {
                Log.Error(() => $"A general http error occurred in RequestHeadlessAuthentication state={state} nonce={nonce} authUrl={discoveryResponse.OperatorUrls.AuthorizationUrl} tokenUrl={discoveryResponse.OperatorUrls.RequestTokenUrl}");
                return(MobileConnectStatus.Error(ErrorCodes.HttpFailure, "An HTTP failure occured while calling the discovery endpoint, the endpoint may be inaccessible", e));
            }
            catch (Exception e)
            {
                Log.Error(() => $"A general error occurred in RequestHeadlessAuthentication state={state} nonce={nonce} authUrl={discoveryResponse.OperatorUrls.AuthorizationUrl} tokenUrl={discoveryResponse.OperatorUrls.RequestTokenUrl}");
                return(MobileConnectStatus.Error(ErrorCodes.Unknown, "An unknown error occured while generating an authorization url", e));
            }

            if (status.ResponseType == MobileConnectResponseType.Error ||
                !options.AutoRetrieveIdentityHeadless || string.IsNullOrEmpty(discoveryResponse.OperatorUrls.PremiumInfoUrl))
            {
                return(status);
            }

            var identityStatus = await RequestIdentity(identity, discoveryResponse, status.TokenResponse.ResponseData.AccessToken, config, options);

            status.IdentityResponse = identityStatus.IdentityResponse;

            return(status);
        }