public async Task Token_endpoint_supports_client_authentication_with_basic_authentication_with_POST()
        {
            await LoginAsync("bob");

            var nonce = Guid.NewGuid().ToString();

            _browser.AllowAutoRedirect = false;
            var url = CreateAuthorizeUrl(
                           clientId: "code_client",
                           responseType: "code",
                           scope: "openid",
                           redirectUri: "https://code_client/callback?foo=bar&baz=quux",
                           nonce: nonce);
            var response = await _client.GetAsync(url);

            var authorization = ParseAuthorizationResponseUrl(response.Headers.Location.ToString());
            authorization.Code.Should().NotBeNull();

            var code = authorization.Code;

            // backchannel client
            var wrapper = new MessageHandlerWrapper(_server.CreateHandler());
            var tokenClient = new TokenClient(TokenEndpoint, "code_client", "secret", wrapper);
            var tokenResult = await tokenClient.RequestAuthorizationCodeAsync(code, "https://code_client/callback?foo=bar&baz=quux");

            tokenResult.IsError.Should().BeFalse();
            tokenResult.IsHttpError.Should().BeFalse();
            tokenResult.TokenType.Should().Be("Bearer");
            tokenResult.AccessToken.Should().NotBeNull();
            tokenResult.ExpiresIn.Should().BeGreaterThan(0);
            tokenResult.IdentityToken.Should().NotBeNull();

            wrapper.Response.Headers.CacheControl.NoCache.Should().BeTrue();
            wrapper.Response.Headers.CacheControl.NoStore.Should().BeTrue();
        }
        public async Task<ActionResult> GetToken()
        {
            var client = new TokenClient(
                Constants.TokenEndpoint,
                "codeclient",
                "secret");

            var code = Request.QueryString["code"];
            var tempState = await GetTempStateAsync();
            Request.GetOwinContext().Authentication.SignOut("TempState");

            var response = await client.RequestAuthorizationCodeAsync(
                code,
                "https://localhost:44312/callback");

            await ValidateResponseAndSignInAsync(response, tempState.Item2);

            if (!string.IsNullOrEmpty(response.IdentityToken))
            {
                ViewBag.IdentityTokenParsed = ParseJwt(response.IdentityToken);
            }
            if (!string.IsNullOrEmpty(response.AccessToken))
            {
                ViewBag.AccessTokenParsed = ParseJwt(response.AccessToken);
            }

            return View("Token", response);
        }
        public async Task<ActionResult> SignInToken()
        {
            var client = new TokenClient(Constants.TokenEndpoint,
                "_test",
                "8XBxsZWA4z8YicTXZ0meRE/Pm03tXLDXLUw6ofyvjg8=");

            var code = Request.Form["code"];
            //var tempState = await GetTem

            var response = await client.RequestAuthorizationCodeAsync(code, "https://localhost:44300/Account/SignInToken");


            return RedirectToAction("SignInToken", new { token = response });
        }
Example #4
0
        public async Task<ActionResult> Index()
        {
	        var authCode = Request.QueryString["code"];

	        var client = new TokenClient(IdealConstants.STSTokenEndpoint,
				IdealConstants.ClientId,
				IdealConstants.ClientSecret);

	        var tokenResponse = await client.RequestAuthorizationCodeAsync(
		        authCode,
				IdealConstants.ClientCallbackUrl);

	        Response.Cookies["ideal.auth"]["access_token"] = tokenResponse.AccessToken;

            return Redirect(Request.QueryString["state"]);
        }
        private async void UseCodeButton_Click(object sender, RoutedEventArgs e)
        {
            if (_response != null && _response.Values.ContainsKey("code"))
            {
                var client = new TokenClient(
                    Constants.TokenEndpoint,
                    "hybridclient",
                    "secret");

                var response = await client.RequestAuthorizationCodeAsync(
                    _response.Code,
                    "oob://localhost/wpfclient");

                Textbox1.Text = response.Json.ToString();
            }
        }
        private static async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
        {
            // 1) Use the code to get the access and refresh token, 
            // As we are using the hybrid flow, we will get a "code" and "access_token" but not "refresh_token".
            // Using the code we can get a "refresh_token" if the client application is a server side app (like this example)
            // If the application is a SPA or a native phone app, it is not secure to use the ClientSecret 
            var tokenClient = new TokenClient(Constants.TokenEndpoint, Constants.ClientId, Constants.ClientSecret);
            var tokensResponse = tokenClient.RequestAuthorizationCodeAsync(context.Code, context.RedirectUri).Result;

            var expiration = DateTime.Now.AddSeconds(tokensResponse.ExpiresIn)
                .ToLocalTime()
                .ToString(CultureInfo.InvariantCulture);

            List<Claim> oauthClaims = new List<Claim>
            {
                new Claim("access_token", tokensResponse.AccessToken),
                new Claim("refresh_token", tokensResponse.RefreshToken),
                new Claim("expires_at", expiration)
            };

            // 2) Use the access token to retrieve user info claims
            // The access token is a JWT token, it can be used to secure WebApi
            var userInfoClient = new UserInfoClient(new Uri(Constants.UserInfoEndpoint), tokensResponse.AccessToken);
            var userInfo = await userInfoClient.GetAsync();
            List<Claim> userClaims = userInfo.Claims.Select(ui => new Claim(ui.Item1, ui.Item2)).ToList();

            // 3) Add claims to authentication ticket
            ClaimsIdentity identity = context.AuthenticationTicket.Principal.Identity as ClaimsIdentity;
            if (identity != null)
            {
                // Remove all protocol related claims
                var claimsToRemove = identity.Claims.ToList();
                foreach (var claim in claimsToRemove)
                {
                    identity.RemoveClaim(claim);
                }

                // Add oauth and user claims
                identity.AddClaims(oauthClaims);
                identity.AddClaims(userClaims);
            }
        }
        // GET: STSCallback
        public async Task<ActionResult> Index()
        {

            var authCode = Request.QueryString["code"];

            var client = new TokenClient(
                    TripGallery.Constants.TripGallerySTSTokenEndpoint,
                    "tripgalleryauthcode",
                    TripGallery.Constants.TripGalleryClientSecret
                );


            var tokenResponse = await client.RequestAuthorizationCodeAsync(
                                    authCode,
                                    TripGallery.Constants.TripGalleryMVCSTSCallback);

            Response.Cookies["TripGalleryCookie"]["access_token"] = tokenResponse.AccessToken;

            return Redirect(Request.QueryString["state"]);
        }
        /// <summary>
        ///   Richiede una batteria di token partendo da un dato authorization code.
        /// </summary>
        /// <param name="owinContext">Il contesto HTTP di OWIN.</param>
        /// <param name="clientId">Il client ID a cui sono legati i token.</param>
        /// <param name="authorizationCode">L'authorization code.</param>
        /// <returns>La batteria di token recuperati partendo dall'authorization code.</returns>
        public async Task<OAuth2TokensGenerationResult> RequestTokensAsync(IOwinContext owinContext, string clientId, string authorizationCode)
        {
            // Registro la data di inizio in maniera conservativa.
            var accessTokenCreatedOn = _clock.UtcNow;

            var address = AuthorizationSettings.AccessTokenRequestEndpointUri.AbsoluteUri;
            var clientSecret = AuthorizationSettings.Clients[clientId].ClientSecret;

            // Attivo il client per il recupero dei token.
            var tokenClient = new TokenClient(address, clientId, clientSecret);
            var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(authorizationCode, owinContext.Request.Uri.AbsoluteUri);

            if (tokenResponse.IsError)
            {
                Log.Error(tokenResponse.Error);
                throw new Exception(tokenResponse.Error);
            }

            // Copio tutti i token ricevuti in un cookie.
            var oauth2TokensCookie = new OAuth2TokensCookie
            {
                AccessToken = tokenResponse.AccessToken,
                AccessTokenCreatedOn = accessTokenCreatedOn,
                AccessTokenExpiresIn = tokenResponse.ExpiresIn,
                RefreshToken = tokenResponse.RefreshToken,
                RandomToken = Guid.NewGuid()
            };

            // Recupero l'identity token, se presente.
            var identityToken = string.IsNullOrWhiteSpace(tokenResponse.IdentityToken)
                ? Option.None<string>()
                : Option.Some(tokenResponse.IdentityToken);

            return new OAuth2TokensGenerationResult
            {
                TokensCookie = oauth2TokensCookie,
                IdentityToken = identityToken
            };
        }
        public async Task Token_endpoint_supports_client_authentication_with_form_encoded_authentication_in_POST_body()
        {
            await _pipeline.LoginAsync("bob");

            var nonce = Guid.NewGuid().ToString();

            _pipeline.BrowserClient.AllowAutoRedirect = false;
            var url = _pipeline.CreateAuthorizeUrl(
                           clientId: "code_pipeline.Client",
                           responseType: "code",
                           scope: "openid",
                           redirectUri: "https://code_pipeline.Client/callback?foo=bar&baz=quux",
                           nonce: nonce);
            var response = await _pipeline.BrowserClient.GetAsync(url);

            var authorization = _pipeline.ParseAuthorizationResponseUrl(response.Headers.Location.ToString());
            authorization.Code.Should().NotBeNull();

            var code = authorization.Code;

            // backchannel client
            var tokenClient = new TokenClient(MockAuthorizationPipeline.TokenEndpoint, "code_pipeline.Client", "secret", _pipeline.Handler, AuthenticationStyle.PostValues);
            var tokenResult = await tokenClient.RequestAuthorizationCodeAsync(code, "https://code_pipeline.Client/callback?foo=bar&baz=quux");

            tokenResult.IsError.Should().BeFalse();
            tokenResult.IsHttpError.Should().BeFalse();
            tokenResult.TokenType.Should().Be("Bearer");
            tokenResult.AccessToken.Should().NotBeNull();
            tokenResult.ExpiresIn.Should().BeGreaterThan(0);
            tokenResult.IdentityToken.Should().NotBeNull();
        }
        public void Configuration(IAppBuilder app)
        {
            JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

            app.UseCookieAuthentication(new CookieAuthenticationOptions
                {
                    AuthenticationType = "Cookies"
                });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
                {
                    ClientId = "katanaclient",
                    Authority = Constants.BaseAddress,
                    RedirectUri = "http://localhost:2672/",
                    PostLogoutRedirectUri = "http://localhost:2672/",
                    ResponseType = "code id_token token",
                    Scope = "openid email profile read write offline_access",

                    SignInAsAuthenticationType = "Cookies",

                    Notifications = new OpenIdConnectAuthenticationNotifications
                    {
                        AuthorizationCodeReceived = async n =>
                            {
                                // get userinfo data
                                var userInfoClient = new UserInfoClient(
                                    new Uri(Constants.UserInfoEndpoint),
                                    n.ProtocolMessage.AccessToken);

                                var userInfo = await userInfoClient.GetAsync();
                                var id = userInfo.GetClaimsIdentity();
                                
                                // get access and refresh token
                                var tokenClient = new TokenClient(
                                    Constants.TokenEndpoint,
                                    "katanaclient",
                                    "secret");

                                var response = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);

                                id.AddClaim(new Claim("access_token", response.AccessToken));
                                id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(response.ExpiresIn).ToLocalTime().ToString()));
                                id.AddClaim(new Claim("refresh_token", response.RefreshToken));
                                id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));

                                n.AuthenticationTicket = new AuthenticationTicket(
                                    new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType), 
                                    n.AuthenticationTicket.Properties);
                            },

                        RedirectToIdentityProvider = n =>
                            {
                                // if signing out, add the id_token_hint
                                if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
                                {
                                    var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");

                                    if (idTokenHint != null)
                                    {
                                        n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                                    }

                                }

                                return Task.FromResult(0);
                            }
                    }
                });
        }
Example #11
0
        private async Task<LoginResult> ValidateAsync(AuthorizeResult result)
        {
            // validate identity token
            var principal = await ValidateIdentityTokenAsync(result.IdentityToken);
            if (principal == null)
            {
                return new LoginResult
                {
                    Success = false,
                    Error = "identity token validation error"
                };
            }

            // validate nonce
            var tokenNonce = principal.FindFirst("nonce")?.Value ?? "";
            if (!string.Equals(result.Nonce, tokenNonce))
            {
                return new LoginResult
                {
                    Success = false,
                    Error = "invalid nonce"
                };
            }

            // validate audience
            var audience = principal.FindFirst("aud")?.Value ?? "";
            if (!string.Equals(_options.ClientId, audience))
            {
                return new LoginResult
                {
                    Success = false,
                    Error = "invalid audience"
                };
            }

            // validate c_hash
            var cHash = principal.FindFirst("c_hash")?.Value ?? "";

            var sha256 = HashAlgorithmProvider.OpenAlgorithm("SHA256");

            var codeHash = sha256.HashData(
                CryptographicBuffer.CreateFromByteArray(
                    Encoding.ASCII.GetBytes(result.Code)));

            byte[] codeHashArray;
            CryptographicBuffer.CopyToByteArray(codeHash, out codeHashArray);

            byte[] leftPart = new byte[16];
            Array.Copy(codeHashArray, leftPart, 16);

            var leftPartB64 = Base64Url.Encode(leftPart);

            if (!leftPartB64.Equals(cHash))
            {
                return new LoginResult
                {
                    Success = false,
                    Error = "invalid code"
                };
            }

            var endpoints = await _options.GetEndpointsAsync();

            // get access token
            var tokenClient = new TokenClient(endpoints.Token, _options.ClientId, _options.ClientSecret);
            var tokenResult = await tokenClient.RequestAuthorizationCodeAsync(
                result.Code, 
                result.RedirectUri, 
                codeVerifier: result.Verifier);

            if (tokenResult.IsError || tokenResult.IsHttpError)
            {
                return new LoginResult
                {
                    Success = false,
                    Error = tokenResult.Error
                };
            }

            // get profile if enabled
            if (_options.LoadProfile)
            {
                var userInfoClient = new UserInfoClient(new Uri(endpoints.UserInfo), tokenResult.AccessToken);
                var userInfoResponse = await userInfoClient.GetAsync();

                var primaryClaimTypes = principal.Claims.Select(c => c.Type).Distinct();

                foreach (var claim in userInfoResponse.Claims.Where(c => !primaryClaimTypes.Contains(c.Item1)))
                {
                    principal.Identities.First().AddClaim(new Claim(claim.Item1, claim.Item2));
                }

            }

            // success
            return new LoginResult
            {
                Success = true,
                Principal = FilterProtocolClaims(principal),
                AccessToken = tokenResult.AccessToken,
                RefreshToken = tokenResult.RefreshToken,
                AccessTokenExpiration = DateTime.Now.AddSeconds(tokenResult.ExpiresIn),
                IdentityToken = result.IdentityToken,
                AuthenticationTime = DateTime.Now
            };
        }
Example #12
0
        private async System.Threading.Tasks.Task<TokenResponse> GetAuthTokenAsync(AuthorizeResponse response)
        {
            var redirectUri = WebAuthenticationBroker.GetCurrentApplicationCallbackUri();

            // Let's get the actual (short lived) access token using the authorization code.
            var tokenClient = new TokenClient(
                            TimesheetConstants.TokenEndpoint,
                            TimesheetConstants.ClientId,
                            "mysupersecretkey");

            return await tokenClient.RequestAuthorizationCodeAsync(response.Code, redirectUri.ToString());
        }
        public void Configuration(IAppBuilder app)
        {
            JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "Cookies"
            });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                ClientId = "mvc.owin.hybrid",
                Authority = Constants.BaseAddress,
                RedirectUri = "https://localhost:44300/",
                PostLogoutRedirectUri = "https://localhost:44300/",
                ResponseType = "code id_token",
                Scope = "openid profile read write offline_access",

                TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = "name",
                    RoleClaimType = "role"
                },

                SignInAsAuthenticationType = "Cookies",

                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthorizationCodeReceived = async n =>
                        {
                            // use the code to get the access and refresh token
                            var tokenClient = new TokenClient(
                                Constants.TokenEndpoint,
                                "mvc.owin.hybrid",
                                "secret");

                            var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
                                n.Code, n.RedirectUri);

                            if (tokenResponse.IsError)
                            {
                                throw new Exception(tokenResponse.Error);
                            }

                            // use the access token to retrieve claims from userinfo
                            var userInfoClient = new UserInfoClient(
                            new Uri(Constants.UserInfoEndpoint),
                            tokenResponse.AccessToken);

                            var userInfoResponse = await userInfoClient.GetAsync();

                            // create new identity
                            var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
                            id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);

                            id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
                            id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
                            id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                            id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
                            id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));

                            n.AuthenticationTicket = new AuthenticationTicket(
                                new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
                                n.AuthenticationTicket.Properties);
                        },

                    RedirectToIdentityProvider = n =>
                        {
                            // if signing out, add the id_token_hint
                            if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
                            {
                                var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");

                                if (idTokenHint != null)
                                {
                                    n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                                }

                            }

                            return Task.FromResult(0);
                        }
                }
            });
        }
        public void Configuration(IAppBuilder app)
        {
            string baseAddress = "https://localhost:44333/core";
            string tokenEndpoint = baseAddress + "/connect/token";
            string userInfoEndpoint = baseAddress + "/connect/userinfo";
            string clientAddress = "https://localhost:44302/";

            JwtSecurityTokenHandler.InboundClaimTypeMap =
                new Dictionary<string, string>();

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "Cookies"
            });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
            {
                ClientId = "katanaclient",
                Authority = baseAddress,
                RedirectUri = clientAddress,
                PostLogoutRedirectUri = clientAddress,
                ResponseType = "code id_token",
                Scope = "openid profile roles read write offline_access",
                SignInAsAuthenticationType = "Cookies",

                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthorizationCodeReceived = async n =>
                    {
                        var tokenClient = new TokenClient(
                            tokenEndpoint,
                            "katanaclient",
                            "secret");

                        var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
                            n.Code, n.RedirectUri);

                        if (tokenResponse.AccessToken != null)
                        {
                            var userInfoClient = new UserInfoClient(
                                new Uri(userInfoEndpoint), tokenResponse.AccessToken);

                            var userInfoResponse = await userInfoClient.GetAsync();

                            var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
                            var userClaims = userInfoResponse.GetClaimsIdentity().Claims.ToList();

                            if (userClaims.Any())
                            {
                                id.AddClaims(userClaims);
                                foreach (var claim in userClaims.Where(x => x.Type == "role"))
                                {
                                    id.AddClaim(new Claim(ClaimTypes.Role, claim.Value));
                                }
                            }
                            if (tokenResponse.AccessToken != null)
                                id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));

                            var localExpiresIn = DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime();
                            if (localExpiresIn != null)
                            {
                                id.AddClaim(new Claim("expires_at",
                                    localExpiresIn
                                        .ToString(CultureInfo.InvariantCulture)));
                            }

                            if (tokenResponse.RefreshToken != null)
                                id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                            if (n.ProtocolMessage.IdToken != null)
                                id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
                            id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity
                                .FindFirst("sid").Value));

                            n.AuthenticationTicket = new AuthenticationTicket(
                                new ClaimsIdentity(id.Claims,
                                    n.AuthenticationTicket.Identity.AuthenticationType),
                                n.AuthenticationTicket.Properties);
                        }
                    },
                    RedirectToIdentityProvider = n =>
                    {
                        if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
                        {
                            var idTokenHint = n.OwinContext.Authentication.User
                                .FindFirst("id_token");
                            if (idTokenHint != null)
                            {
                                n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                            }
                        }
                        return Task.FromResult(0);
                    }
                }

            });
        }
        private async Task<LoginResult> ValidateResponseAsync(AuthorizeResponse response)
        {
            // id_token validieren
            var tokenClaims = ValidateIdentityToken(response.IdentityToken);

            if (tokenClaims == null)
            {
                return new LoginResult { ErrorMessage = "Invalid identity token." };
            }

            // nonce validieren
            var nonce = tokenClaims.FirstOrDefault(c => c.Type == JwtClaimTypes.Nonce);

            if (nonce == null || !string.Equals(nonce.Value, _nonce, StringComparison.Ordinal))
            {
                return new LoginResult { ErrorMessage = "Inalid nonce." };
            }

            // c_hash validieren
            var c_hash = tokenClaims.FirstOrDefault(c => c.Type == JwtClaimTypes.AuthorizationCodeHash);

            if (c_hash == null || ValidateCodeHash(c_hash.Value, response.Code) == false)
            {
                return new LoginResult { ErrorMessage = "Invalid code." };
            }

            // code eintauschen gegen tokens
            var tokenClient = new TokenClient(
                _config.TokenEndpoint,
                _settings.ClientId,
                _settings.ClientSecret);

            var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
                code: response.Code,
                redirectUri: _settings.RedirectUri,
                codeVerifier: _verifier);

            if (tokenResponse.IsError)
            {
                return new LoginResult { ErrorMessage = tokenResponse.Error };
            }

            // optional userinfo aufrufen
            var profileClaims = new List<Claim>();
            if (_settings.LoadUserProfile)
            {
                var userInfoClient = new UserInfoClient(
                    new Uri(_config.UserInfoEndpoint),
                    tokenResponse.AccessToken);

                var userInfoResponse = await userInfoClient.GetAsync();
                profileClaims = userInfoResponse.GetClaimsIdentity().Claims.ToList();
            }

            var principal = CreatePrincipal(tokenClaims, profileClaims);

            return new LoginResult
            {
                Success = true,
                User = principal,
                IdentityToken = response.IdentityToken,
                AccessToken = tokenResponse.AccessToken,
                RefreshToken = tokenResponse.RefreshToken,
                AccessTokenExpiration = DateTime.Now.AddSeconds(tokenResponse.ExpiresIn)
            };
        }