예제 #1
0
        public static async Task <BoolValue <JsonWebKeySet> > DownloadAsync(string url)
        {
            using var client  = new HttpClient();
            using var message = new HttpRequestMessage(HttpMethod.Get, url);
            message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/jwk-set+json"));
            try
            {
                var response = await client.SendAsync(message);

                if (!response.IsSuccessStatusCode)
                {
                    return(BoolValue <JsonWebKeySet> .Fail($"Error connecting to {url}: {response.ReasonPhrase}"));
                }

                var content = await response.Content.ReadAsStringAsync();

                var jsonWebKeySet = JsonConvert.DeserializeObject <JsonWebKeySet>(content);
                return(BoolValue <JsonWebKeySet> .Success(jsonWebKeySet));
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                throw;
            }
        }
예제 #2
0
        public async Task <BoolValue <ClaimsPrincipal> > ValidateAsync(string idToken, JwtTokenValidationOptions options = null)
        {
            try
            {
                var discoveryDocument = await DiscoveryDocument.DownloadAsync(idToken);

                if (!discoveryDocument)
                {
                    return(BoolValue <ClaimsPrincipal> .Fail(discoveryDocument.Message, discoveryDocument.Exception));
                }

                var jwks = await JsonWebKeySet.DownloadAsync(discoveryDocument.Value.JwksUri);

                if (!jwks)
                {
                    return(BoolValue <ClaimsPrincipal> .Fail(jwks.Message, jwks.Exception));
                }

                options ??= new JwtTokenValidationOptions();
                var parameters = options.ToTokenValidationParameters(
                    new JwtSecurityToken(idToken),
                    discoveryDocument.Value,
                    jwks.Value);
                var handler = new JwtSecurityTokenHandler();
                handler.InboundClaimTypeMap.Clear();
                var user = handler.ValidateToken(idToken, parameters, out _);
                return(BoolValue <ClaimsPrincipal> .Success(user));
            }
            catch (Exception ex)
            {
                return(BoolValue <ClaimsPrincipal> .Fail(ex.Message, ex));
            }
        }
        /// <inheritdoc />
        public override async Task <BoolValue <AuthResult> > GetAccessTokenAsync(bool allowCached = true)
        {
            if (allowCached)
            {
                var cached = await tryGetCachedAuthResultAsync();

                if (cached && cached.Value.AccessToken != null)
                {
                    return(onAuthorizationDone(cached));
                }
            }

#if DEBUG
            var simulatedAuth = await AuthSimulator.TryGetSimulatedAccessTokenAsync(Config, CacheKey);

            if (simulatedAuth)
            {
                return(simulatedAuth);
            }
#endif

            try
            {
                LogDebug("---- START - Tetra Pak Code Grant Flow ----");
                return(await acquireTokenAsyncUsingNativeWebUI());
            }
            catch (Exception ex)
            {
                LogError(ex, ex.Message);
                LogDebug("---- END - Tetra Pak Code Grant Flow ----");
                return(BoolValue <AuthResult> .Fail("Could not acquire an access token", ex));
            }
        }
        public async Task <BoolValue <string[]> > TeyGetUserInfoTypes()
        {
            if (AccessToken is null)
            {
                _log?.Warning("Cannot get user information without a valid access token");
                return(BoolValue <string[]> .Fail());
            }

            try
            {
                if (_userInformation is { })
                {
                    return(BoolValue <string[]> .Success(_userInformation.Types));
                }

                var discoDoc = DiscoveryDocument.Current;
                if (discoDoc is null)
                {
                    var gotDiscoDoc = await DiscoveryDocument.TryDownloadAndSetCurrent(this, true);

                    if (!gotDiscoDoc)
                    {
                        _log?.Debug("ERROR: Failed to retrieve the discovery document. Cannot resolve user information endpoint");
                        return(BoolValue <string[]> .Fail("Failed to retrieve the discovery document. Cannot resolve user information endpoint"));
                    }
                    discoDoc = gotDiscoDoc.Value;
                }
                _userInfoLoader ??= new UserInfoLoader(AccessToken, discoDoc, _log);
                _userInformation = await _userInfoLoader.AwaitDownloadedAsync();

                return(BoolValue <string[]> .Success(_userInformation.Types));
            }
예제 #5
0
        public static BoolValue <DiscoveryEndpoint> TryParseUrl(string input)
        {
            var success = Uri.TryCreate(input, UriKind.Absolute, out var uri);

            if (success == false)
            {
                var msg = $"Malformed URL: {input}";
                return(BoolValue <DiscoveryEndpoint> .Fail(msg, new FormatException(msg)));
            }

            if (!IsValidScheme(uri))
            {
                var msg = $"Invalid scheme in URL: {input}";
                return(BoolValue <DiscoveryEndpoint> .Fail(msg, new InvalidOperationException(msg)));
            }

            var url       = input.RemoveTrailingSlash();
            var authority = url.EndsWith(WellKnownEndpoint, StringComparison.OrdinalIgnoreCase)
                ? url.Substring(0, url.Length - WellKnownEndpoint.Length - 1)
                : url;

            url = url.EndsWith(WellKnownEndpoint, StringComparison.OrdinalIgnoreCase)
                ? url
                : $"{url.EnsureEndsWith("/")}{WellKnownEndpoint}";


            return(BoolValue <DiscoveryEndpoint> .Success(new DiscoveryEndpoint(authority, url)));

            /* obsolete
             * return url.EndsWith(WellKnownEndpoint, StringComparison.OrdinalIgnoreCase)
             *  ?  new DiscoveryEndpoint(url.Substring(0, url.Length - WellKnownEndpoint.Length - 1), url)
             *  : new DiscoveryEndpoint(url, $"{url.EnsureEndsWith("/")}{WellKnownEndpoint}");
             */
        }
        async Task <BoolValue <AuthResult> > buildAuthResultAsync(string responseText)
        {
            var dict = JsonSerializer.Deserialize <Dictionary <string, string> >(responseText);

            if (!dict.TryGetValue("access_token", out var accessToken))
            {
                return(BoolValue <AuthResult> .Fail("Could not get a valid access token."));
            }

            var tokens  = new List <TokenInfo>();
            var expires = dict.TryGetValue("expires_in", out var exp) && int.TryParse(exp, out var seconds)
                ? DateTime.Now.AddSeconds(seconds - 4)
                : (DateTime?)null;

            tokens.Add(new TokenInfo(accessToken, TokenRole.AccessToken, expires, null));

            if (dict.TryGetValue("refresh_token", out var refreshToken))
            {
                tokens.Add(new TokenInfo(refreshToken, TokenRole.RefreshToken, null, null));
            }

            if (!dict.TryGetValue("id_token", out var idToken))
            {
                return(await cacheAuthResultAsync(BoolValue <AuthResult> .Success(new AuthResult(Config, Log, tokens.ToArray()))));
            }

            /* obsolete (since we made validation available we no longer auto-validates id token)
             * var idTokenValidation = await validateIdTokenAsync(idToken);
             * if (!idTokenValidation)
             *  return BoolValue<AuthResult>.Fail(idTokenValidation.Message);
             */

            tokens.Add(new TokenInfo(idToken, TokenRole.IdToken, null, validateIdTokenAsync));
            return(await cacheAuthResultAsync(BoolValue <AuthResult> .Success(new AuthResult(Config, Log, tokens.ToArray()))));
        }
        /// <inheritdoc />
        public override async Task <BoolValue <AuthResult> > GetAccessTokenSilentlyAsync()
        {
            if (!IsCaching)
            {
                return(await GetAccessTokenAsync());
            }

            var cached = await tryGetCachedAuthResultAsync();

            if (!cached)
            {
                return(await GetAccessTokenAsync(false));
            }

            if (cached.Value.AccessToken != null)
            {
                if (!cached.Value.Expires.HasValue || DateTime.Now < cached.Value.Expires.Value)
                {
                    return(onAuthorizationDone(cached));
                }
            }

            await removeFromCacheAsync();

            if (string.IsNullOrEmpty(cached.Value.RefreshToken))
            {
                return(await GetAccessTokenAsync());
            }

#if DEBUG
            var simulatedAuth = await AuthSimulator.TryGetSimulatedRenewedAccessTokenAsync(cached.Value.RefreshToken, Config, CacheKey);

            if (simulatedAuth)
            {
                return(simulatedAuth);
            }
#endif

            // access token has expired, try renew from refresh token if available ...
            LogDebug("---- START - Tetra Pak Refresh Token Flow ----");
            BoolValue <AuthResult> result;
            try
            {
                result = await acquireRenewedAccessTokenAsync(cached.Value.RefreshToken);
            }
            catch (Exception ex)
            {
                LogError(ex, ex.Message);
                return(BoolValue <AuthResult> .Fail("Could not renew access token", ex));
            }
            finally
            {
                LogDebug("---- END - Tetra Pak Refresh Token Flow ----");
            }

            return(result ? result : await GetAccessTokenAsync());
        }
        public static async Task <BoolValue <AuthResult> > TryGetSimulatedAccessTokenAsync(AuthConfig config, string cacheKey)
        {
            if (!IsSimulating)
            {
                return(BoolValue <AuthResult> .Fail());
            }

            return(await GetAccessTokenAsync(config, cacheKey));
        }
        static async Task <BoolValue <string> > validateIdTokenAsync(string idToken)
        {
            var validator = new IdTokenValidator();
            var validated = await validator.ValidateAsync(idToken);

            return(validated
                ? BoolValue <string> .Success(idToken)
                : BoolValue <string> .Fail(validated.Message, validated.Exception));
        }
        async Task <BoolValue <AuthResult> > acquireTokenAsyncUsingNativeWebUI()
        {
            LogDebug("[GET AUTH CODE BEGIN]");
            LogDebug($"Listens for callbacks on {Config.RedirectUri} ...");

            var authAppDelegate = Authorization.GetAuthorizingAppDelegate();

            if (authAppDelegate is null)
            {
                LogDebug("Authorization fails: Could not get an authorization app delegate");
                return(BoolValue <AuthResult> .Fail($"Cannot obtain a {typeof(IAuthorizingAppDelegate)}."));
            }

            var callbackHandler = (TetraPakAuthCallbackHandler)DependencyService.Get <IAuthCallbackHandler>();

            callbackHandler.NotifyUriCallback(onUriCallback);

            // make the call for auth code and await callback from redirect ...
            var authState   = new AuthState(Config.IsStateUsed, Config.IsPkceUsed, Config.ClientId);
            var authRequest = buildAuthRequest(authState);

            LogDebug(authRequest);

            await authAppDelegate.OpenInDefaultBrowserAsync(new Uri(authRequest), Config.RedirectUri);

            var callback = await _authCodeTcs.Task.ConfigureAwait(false);

            LogDebug($"Callback notified with value: {callback.Value}");

            // check the PKCE and get the access code ...
            var authCode = callback.Value.TryGetQueryValue("code").Value;
            var inState  = callback.Value.TryGetQueryValue("state").Value;

            LogDebug("[GET AUTH CODE END]");
            if (authState.IsUsed && inState != authState.State)
            {
                return(BoolValue <AuthResult> .Fail($"Returned state was invalid: \"{inState}\". Expected state: \"{authState.State}\""));
            }

            LogDebug("[GET ACCESS CODE BEGIN]");
            var accessCodeResult = await getAccessCode(authCode, authState);

            LogDebug("[GET ACCESS CODE END]");
            return(onAuthorizationDone(accessCodeResult));

            void onUriCallback(Uri uri, out bool isHandled)
            {
                if (!uri.Scheme.Equals(Config.RedirectUri.Scheme) || !uri.Authority.Equals(Config.RedirectUri.Authority))
                {
                    isHandled = false;
                    return;
                }
                isHandled = true;
                _authCodeTcs.SetResult(BoolValue <Uri> .Success(uri));
            }
        }
예제 #11
0
        protected override BoolValue <string> OnValidateValue(string value)
        {
            value = value?.Trim();
            if (string.IsNullOrEmpty(value) && IsRequired)
            {
                return(BoolValue <string> .Fail("This value is required"));
            }

            return(BoolValue <string> .Success(value));
        }
        public static Task <BoolValue <AuthResult> > TryGetFromRefreshTokenAsync(this AuthConfig config, string refreshToken)
        {
            if (!s_authResults.TryGetValue(refreshToken, out var authResult))
            {
                return(Task.FromResult <BoolValue <AuthResult> >(BoolValue <AuthResult> .Fail()));
            }

            s_authResults.Remove(refreshToken);
            return(Task.FromResult(BoolValue <AuthResult> .Success(authResult)));
        }
        async Task <BoolValue <AuthResult> > tryGetCachedAuthResultAsync()
        {
            if (!IsCaching)
            {
                return(BoolValue <AuthResult> .Fail());
            }

            var cached = await Config.TokenCache.TryGetAsync(CacheKey);

            return(cached ? cached : BoolValue <AuthResult> .Fail());
        }
예제 #14
0
        protected override BoolValue <string> OnValidateValue(string value)
        {
            value = value?.Trim();
            if (string.IsNullOrEmpty(value))
            {
                return(base.OnValidateValue(value));
            }

            return(!Uri.TryCreate(value, UriKind.Absolute, out _)
                ? BoolValue <string> .Fail("Invalid absolute URI")
                : BoolValue <string> .Success(value));
        }
예제 #15
0
 static BoolValue <DiscoveryEndpoint> tryResolveUrlFromAssumedJwtToken(string input)
 {
     try
     {
         var jwtToken = new JwtSecurityToken(input);
         return(TryParseUrl(jwtToken.Issuer));
     }
     catch (Exception ex)
     {
         return(BoolValue <DiscoveryEndpoint> .Fail($"Cannot resolve discovery endpoint from: \"{input}\"", ex));
     }
 }
        public static async Task <BoolValue <AuthResult> > TryGetSimulatedRenewedAccessTokenAsync(string refreshToken, AuthConfig config, string cacheKey)
        {
            var canBeRefreshed = await config.TryGetFromRefreshTokenAsync(refreshToken);

            if (!canBeRefreshed)
            {
                return(BoolValue <AuthResult> .Fail($"Invalid refresh token"));
            }

            var accessToken = new TokenInfo(new RandomString(), TokenRole.AccessToken, DateTime.Now.Add(AccessTokenLongevity), null);

            return(await config.CacheAsync(new AuthResult(null, null, accessToken), cacheKey));
        }
        /// <summary>
        ///   Attempts obtaining user information.
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// </remarks>
        public async Task <BoolValue <UserInformation> > TryGetUserInformationAsync()
        {
            _log?.Debug("[GET USER INFORMATION BEGIN]");

            if (AccessToken is null)
            {
                _log?.Warning("Cannot get user information without a valid access token");
                return(BoolValue <UserInformation> .Fail());
            }

            if (_userInformation != null)
            {
                _log?.Debug("User information was cached");
                return(BoolValue <UserInformation> .Success(_userInformation));
            }

            try
            {
                _log?.Debug("Retrieves user information from API ...");
                var discoDoc = DiscoveryDocument.Current;
                if (discoDoc is null)
                {
                    var gotDiscoDoc = await DiscoveryDocument.TryDownloadAndSetCurrent(this, true);

                    if (!gotDiscoDoc)
                    {
                        _log?.Debug("ERROR: Failed to retrieve the discovery document. Cannot resolve user information endpoint");
                        return(BoolValue <UserInformation> .Fail("Failed to retrieve the discovery document. Cannot resolve user information endpoint"));
                    }

                    discoDoc = gotDiscoDoc.Value;
                }

                _userInfoLoader ??= new UserInfoLoader(AccessToken, discoDoc, _log);
                _userInformation = await _userInfoLoader.AwaitDownloadedAsync();

                _log?.Debug("Successfully received user information from API");
                return(BoolValue <UserInformation> .Success(_userInformation));
            }
            catch (Exception ex)
            {
                const string Message = "Failed while retrieving user information from API";
                _log?.Error(ex, Message);
                return(BoolValue <UserInformation> .Fail(Message, ex));
            }
            finally
            {
                _log?.Debug("[GET USER INFORMATION END]");
            }
        }
        async Task <BoolValue <AuthResult> > getAccessCode(string authCode, AuthState authState)
        {
            var body    = buildTokenRequestBody(authCode, authState);
            var uri     = Config.TokenIssuer.AbsoluteUri;
            var request = (HttpWebRequest)WebRequest.Create(uri);

            request.Method      = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.Accept      = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            var bodyData = Encoding.ASCII.GetBytes(body);

            request.ContentLength = bodyData.Length;

            Log.DebugWebRequest(request, body);

            var stream = request.GetRequestStream();
            await stream.WriteAsync(bodyData, 0, bodyData.Length);

            stream.Close();
            Log?.DebugWebRequest(request, body);
            try
            {
                var response = await request.GetResponseAsync();

                var responseStream = response.GetResponseStream()
                                     ?? throw new Exception("Unexpected error: No response when requesting token.");

                using (var r = new StreamReader(responseStream))
                {
                    var text = await r.ReadToEndAsync();

                    Log?.DebugWebResponse(response as HttpWebResponse, text);
                    return(await buildAuthResultAsync(text));
                }
            }
            catch (WebException webException)
            {
                var response = (HttpWebResponse)webException.Response;
                return(BoolValue <AuthResult> .Fail($"Server error: {(int)response.StatusCode} {response.StatusCode.ToString().SplitCamelCase()}"));
            }
            catch (Exception ex)
            {
                return(BoolValue <AuthResult> .Fail($"Unexpected Server error: {ex}"));
            }
        }
        async Task <BoolValue <AuthResult> > acquireRenewedAccessTokenAsync(string refreshToken)
        {
            var body    = makeRefreshTokenBody(refreshToken, Config.IsPkceUsed);
            var uri     = Config.TokenIssuer;
            var request = (HttpWebRequest)WebRequest.Create(uri);

            request.Method      = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.Accept      = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            var bodyData = Encoding.ASCII.GetBytes(body);

            request.ContentLength = bodyData.Length;
            var stream = request.GetRequestStream();
            await stream.WriteAsync(bodyData, 0, bodyData.Length);

            stream.Close();

            Log?.DebugWebRequest(request, body);
            try
            {
                var response = await request.GetResponseAsync();

                var responseStream = response.GetResponseStream()
                                     ?? throw new Exception("Unexpected error: No response when requesting token.");

                using (var r = new StreamReader(responseStream))
                {
                    var text = await r.ReadToEndAsync();

                    Log.DebugWebResponse(response as HttpWebResponse, text);
                    return(await buildAuthResultAsync(text));
                }
            }
            catch (Exception ex)
            {
                LogDebug($"Failed request");
                return(BoolValue <AuthResult> .Fail("Could not get a valid access token.", ex));
            }
        }