/// <summary>
        /// Se llama para validar que se haya enviado un cliente válido en la petición del token
        /// </summary>
        /// <param name="context">Contexto de la petición</param>
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            string clientId     = string.Empty;
            string clientSecret = string.Empty;

            if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
            {
                context.TryGetFormCredentials(out clientId, out clientSecret);
            }

            if (context.ClientId == null)
            {
                //Remove the comments from the below line context.SetError, and invalidate context
                //if you want to force sending clientId/secrects once obtain access tokens.
                //context.Validated();
                //return Task.FromResult<object>(null);

                context.Rejected();
                context.SetError("invalid_clientId", "ClientId should be sent.");
                return;
            }

            context.Validated();
        }
Example #2
0
        /// <summary>
        /// Called to validate that the origin of the request is a registered "client_id", and that the correct credentials for that client are
        /// present on the request. If the web application accepts Basic authentication credentials,
        /// context.TryGetBasicCredentials(out clientId, out clientSecret) may be called to acquire those values if present in the request header. If the web
        /// application accepts "client_id" and "client_secret" as form encoded POST parameters,
        /// context.TryGetFormCredentials(out clientId, out clientSecret) may be called to acquire those values if present in the request body.
        /// If context.Validated is not called the request will not proceed further.
        /// </summary>
        /// <param name="context">The context of the event carries information in and results out.</param>
        /// <returns>Task to enable asynchronous execution</returns>
        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            //TODO: This would work but would it be better to just have two different endpoints setup - one for members, one for users.
            //      Each of those would then support the "password", "authorization_code" grant_type

            //check for a custom parameter in the request which will dictate how with authenticate
            var uid = context.Parameters.Where(f => f.Key == "umb_auth").Select(f => f.Value).SingleOrDefault();

            if (uid == null || uid.Length == 0)
            {
                uid = new[] { UmbracoAuthType.UserPassword.ToString() };
            }
            context.OwinContext.Set("umb:authtype", uid[0]);

            var tokenEndpointRequest = new TokenEndpointRequest(context.Parameters);

            if (tokenEndpointRequest.IsAuthorizationCodeGrantType)
            {
                if (!context.TryGetBasicCredentials(out var clientId, out _))
                {
                    context.TryGetFormCredentials(out clientId, out _);
                }

                if (context.ClientId == null)
                {
                    context.Rejected();
                    context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
                    return(Task.FromResult(0));
                }

                context.Validated(clientId);
            }
            else
            {
                context.Validated();
            }

            return(Task.FromResult(0));

            // Called to validate that the origin of the request is a registered "client_id", and that the correct credentials for that client are
            // present on the request. If the web application accepts Basic authentication credentials,
            // context.TryGetBasicCredentials(out clientId, out clientSecret) may be called to acquire those values if present in the request header. If the web
            // application accepts "client_id" and "client_secret" as form encoded POST parameters,
            // context.TryGetFormCredentials(out clientId, out clientSecret) may be called to acquire those values if present in the request body.
            // If context.Validated is not called the request will not proceed further.

            //** Currently we just accept everything globally
            //context.Validated();
            //return Task.FromResult(0);

            // Example for checking registered clients:

            //** Validate that the data is in the request
            //string clientId;
            //string clientSecret;
            //if (context.TryGetFormCredentials(out clientId, out clientSecret) == false)
            //{
            //    context.SetError("invalid_client", "Form credentials could not be retrieved.");
            //    context.Rejected();
            //    return Task.FromResult(0);
            //}

            //var userManager = context.OwinContext.GetUserManager<BackOfficeUserManager>();

            //** Check if this client id is allowed/registered
            // - lookup in custom table

            //** Verify that the client id and client secret match
            //if (client != null && userManager.PasswordHasher.VerifyHashedPassword(client.ClientSecretHash, clientSecret) == PasswordVerificationResult.Success)
            //{
            //    // Client has been verified.
            //    context.Validated(clientId);
            //}
            //else
            //{
            //    // Client could not be validated.
            //    context.SetError("invalid_client", "Client credentials are invalid.");
            //    context.Rejected();
            //}
        }
Example #3
0
 public static void SetCustomError(this OAuthValidateClientAuthenticationContext context, string msg)
 {
     context.Rejected();
     ResponseWrite(context.Response, msg);
 }
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            if (!context.TryGetBasicCredentials(out _, out var clientSecret))
            {
                context.TryGetFormCredentials(out _, out clientSecret);
            }

            if (context.ClientId == null)
            {
                //Remove the comments from the below line context.SetError, and invalidate context
                //if you want to force sending clientId/secrects once obtain access tokens.
                context.Rejected();
                context.SetError("invalid_clientId", "Chave do cliente deve ser enviada.");
                await Task.FromResult <object>(null);

                return;
            }

            var repo   = new MainContext();
            var client = await repo.ApplicationClients.FirstOrDefaultAsync(f => f.Name.Equals(context.ClientId));

            if (client == null)
            {
                context.SetError("invalid_clientId", $"Cliente '{context.ClientId}' não está registrado no sistema.");
                await Task.FromResult <object>(null);

                return;
            }

            if (client.ApplicationType == ApplicationTypes.NativeConfidential)
            {
                if (string.IsNullOrWhiteSpace(clientSecret))
                {
                    context.SetError("invalid_clientId", "Chave do cliente deveria ser enviada.");
                    await Task.FromResult <object>(null);

                    return;
                }
                if (client.Secret != clientSecret)
                {
                    context.SetError("invalid_clientId", "Chave do cliente é inválida.");
                    await Task.FromResult <object>(null);

                    return;
                }
            }

            if (!client.Active)
            {
                context.SetError("invalid_clientId", "Cliente está desativado.");
                await Task.FromResult <object>(null);

                return;
            }

            context.OwinContext.Set("as:clientAllowedOrigin", client.AllowedOrigin);
            context.OwinContext.Set("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());

            context.Validated();
            await Task.FromResult <object>(null);

            return;
        }
Example #5
0
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            string clientId;
            string clientSecret;
            var    code = context.Parameters.Get("code");

            ApiSettings settings = EngineContext.Current.Resolve <ApiSettings>();

            if (!settings.EnableApi)
            {
                context.SetError("invalid_call", "Could not access private resources on this server!");
                context.Rejected();
            }
            else
            {
                if (!context.TryGetFormCredentials(out clientId, out clientSecret))
                {
                    context.TryGetBasicCredentials(out clientId, out clientSecret);
                }

                string grantType = context.Parameters.Get("grant_type");

                if ((!string.IsNullOrEmpty(clientId) || !string.IsNullOrEmpty(clientSecret) ||
                     !string.IsNullOrEmpty(code)) && !string.IsNullOrEmpty(grantType))
                {
                    IClientService clientService = EngineContext.Current.Resolve <IClientService>();

                    bool valid = false;

                    if (grantType == "refresh_token")
                    {
                        valid = clientService.ValidateClientById(clientId);
                    }
                    else
                    {
                        valid = clientService.ValidateClient(clientId, clientSecret, code);
                    }

                    if (valid)
                    {
                        Client client = clientService.GetClient(clientId);
                        //   _clientId = clientId;

                        if (client.IsActive)
                        {
                            context.OwinContext.Set("oauth:client", client);
                            context.Validated(clientId);
                        }
                    }
                    else
                    {
                        context.SetError("invalid_user", "User not active or invalid!");
                        context.Rejected();
                    }
                }
                else
                {
                    context.SetError("invalid_user", "User not active or invalid!");
                    context.Rejected();
                }
            }
        }
Example #6
0
        public override async Task ValidateClientAuthentication(
            OAuthValidateClientAuthenticationContext context)
        {
            string key;
            var    userName     = context.Parameters.Get("username");
            var    userProvider = new UserProvider(new Models.AlphaMedicContext.AlphaMedicContext());
            var    user         = await userProvider.FindByEmailAsync(userName);

            var mAuth = await userProvider.FindMAuthByIdAsync(user.UserId);

            try
            {
                key = context.Parameters.Get("client");
                if (key == null)
                {
                    throw new Exception();
                }
            }
            catch (Exception)
            {
                context.SetError(
                    "invalid_grant",
                    "type of client is undefined"
                    );
                context.Rejected();
                return;
            }
            switch (key)
            {
            case "desktop":
            {
                if (mAuth == null || (bool)!mAuth.IsUseAuth)
                {
                    await Task.FromResult(context.Validated());

                    return;
                }
                else
                {
                    var mac           = long.Parse(mAuth.MacAdress);
                    var currentMinute = DateTime.Parse(DateTime.Now.ToString("g")).Ticks;

                    var hash = this.hash(currentMinute / mac);
                    if (hash == context.Parameters.Get("code"))
                    {
                        await Task.FromResult(context.Validated());

                        return;
                    }
                    else
                    {
                        context.SetError(
                            "invalid_grant",
                            "The authentification code is invalid"
                            );
                        context.Rejected();
                        return;
                    }
                }
            }

            case "android":
            {
                if (mAuth == null)
                {
                    try
                    {
                        var macAdress = context.Parameters.Get("mac");
                        if (macAdress == null)
                        {
                            throw new Exception();
                        }

                        await userProvider.AddMobileAuthentificatorAsync(
                            new MobileAuthentificator
                            {
                                UserId    = user.UserId,
                                IsUseAuth = false,
                                MacAdress = macAdress
                            }
                            );

                        await Task.FromResult(context.Validated());

                        return;
                    }
                    catch (Exception)
                    {
                        context.SetError(
                            "invalid_grant",
                            "client data is undefined"
                            );
                        context.Rejected();
                        return;
                    }
                }
                break;
            }
                await Task.FromResult(context.Validated());
            }

            HttpResponseMessage result = client.GetAsync(urlParameters).Result;

            if (result.IsSuccessStatusCode)
            {
                TimeResponce time = result.Content.ReadAsAsync <TimeResponce>().Result;
            }
            //var currentMinute = DateTime.Parse(DateTime.Now.ToString("g")).Ticks;
            //var hash = this.hash(currentMinute);
            //if (hash == context.Parameters.First(x => x.Key == "code").Value[0])
            //{
            //    await Task.FromResult(context.Validated());
            //}
            //else
            //{
            //    context.SetError(
            //        "invalid_grant",
            //        "The user name or password is incorrect or user account is inactive."
            //        );
            //    context.Rejected();
            //    return;
            //}
            await Task.FromResult(context.Validated());
        }
Example #7
0
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            try
            {
                string clientId, clientSecret;
                if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
                {
                    context.TryGetFormCredentials(out clientId, out clientSecret);
                }

                App  app             = null;
                bool exceptionThrown = false;
                try
                {
                    app = await _dbContext.Apps.FirstOrDefaultAsync(c => c.ClientId == clientId && c.ClientSecret == clientSecret);
                }
                catch (Exception exception)
                {
                    exceptionThrown = true;
                }

                if (exceptionThrown)
                {
                    context.Rejected();
                }

                else if (app == null)
                {
                    context.Rejected();
                }
                else if (!app.IsOAuth)
                {
                    context.Rejected();
                }
                else if (!app.IsActive)
                {
                    context.Rejected();
                }
                else
                {
                    // var scopes = context.Parameters["scope"]; //todo: skip if SSO



                    context.OwinContext.Set("sidekick.client.name", app.Username);
                    context.OwinContext.Set("sidekick.client.appId", app.Id);
                    context.OwinContext.Set("sidekick.client.appName", app.Name);
                    context.OwinContext.Set("sidekick.client.meta", app.Meta);
                    context.OwinContext.Set("sidekick.client.istrusted", app.IsTrusted);
                    context.OwinContext.Set("sidekick.client.tokenexpiry", app.AccessTokenExpiry);
                    context.OwinContext.Set("sidekick.client.refreshTokenExpiry", app.RefreshTokenExpiry);
                    context.OwinContext.Set("sidekick.client.allowedIps", string.IsNullOrEmpty(app.AllowedIp)?"*":app.AllowedIp);


                    var scopeList = new List <string>();

                    foreach (var scope in app.AppScopes)
                    {
                        scopeList.Add(scope.OAuthScope.Name);
                    }

                    context.OwinContext.Set("sidekick.client.scopes", scopeList);

                    context.Validated();
                }
            }
            catch (Exception)
            {
                context.Rejected();
            }
        }
Example #8
0
        /// <summary>
        /// The route of this method is defined by Settings.TokenEndpointPath endpoint, e.g. /token
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            // Called to validate that the origin of the request is a registered "client_id", and that the correct credentials for that
            // client are present on the request. If the web application accepts Basic authentication credentials,
            // context.TryGetBasicCredentials(out clientId, out clientSecret) may be called to acquire those values if present in the request header.
            // If the web application accepts "client_id" and "client_secret" as form encoded POST parameters,
            // context.TryGetFormCredentials(out clientId, out clientSecret) may be called to acquire those values if present in the request body.
            // If context.Validated is not called the request will not proceed further.

            string clientId     = string.Empty;
            string clientSecret = string.Empty;

            #region Load ClientId and ClientSecret from the request

            bool BodyAuthentication   = false;
            bool HeaderAuthentication = false;

            BodyAuthentication = context.TryGetFormCredentials(out clientId, out clientSecret);
            if (!BodyAuthentication)
            {
                HeaderAuthentication = context.TryGetBasicCredentials(out clientId, out clientSecret);
                if (!HeaderAuthentication)
                {
                    AuthRequest authRequest = context.GetAuthRequest();
                    if (authRequest != null)
                    {
                        clientId     = authRequest.client_id;
                        clientSecret = authRequest.client_secret;
                    }
                }
            }

            if (string.IsNullOrWhiteSpace(clientId))
            {
                // Remove the comments from the below lines if you want to force sending clientId/secrects
                // once access token is obtained
                context.Rejected();
                context.SetError("invalid_clientId", "ClientId should be sent.");
                return(Task.FromResult <object>(null));
            }

            #endregion

            Client client = null;

            #region Validate Client from the database

            using (Db.Operations _repo = new Db.Operations())
            {
                client = _repo.FindClient(clientId);
            }

            if (client == null)
            {
                context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", clientId));
                return(Task.FromResult <object>(null));
            }

            if (client.ApplicationType == Models.ApplicationTypes.NativeConfidential)
            {
                if (string.IsNullOrWhiteSpace(clientSecret))
                {
                    context.SetError("invalid_clientId", "Client secret should be sent.");
                    return(Task.FromResult <object>(null));
                }
                else
                {
                    if (client.Secret != Helper.GetHash(clientSecret))
                    {
                        context.SetError("invalid_clientId", "Client secret is invalid.");
                        return(Task.FromResult <object>(null));
                    }
                }
            }

            if (!client.Active)
            {
                context.SetError("invalid_clientId", "Client is inactive.");
                return(Task.FromResult <object>(null));
            }

            #endregion

            context.OwinContext.Set <string>("as:clientAllowedOrigin", client.AllowedOrigin);
            context.OwinContext.Set <string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());

            context.Validated(clientId);  // This sets context.ClientId parameter
            return(Task.FromResult <object>(null));
        }
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            var       clientId     = string.Empty;
            var       clientSecret = string.Empty;
            ApiClient client       = null;

            // TODO: Kris commented this. Uncomment once the all the code is ready.
            // get the client credentials
            if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
            {
                context.TryGetFormCredentials(out clientId, out clientSecret);
            }

            // decline unauthorized client
            if (context.ClientId == null)
            {
                context.Rejected();
                context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
                return;
            }

            // get the client details from the database
            client = await _repo.FindClient(context.ClientId);

            if (client == null)
            {
                context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
                return;
            }
            if (!client.IsActive)
            {
                context.SetError("invalid_clientId", "Client is inactive.");
                return;
            }

            // validate client only if required
            if (client.ApplicationType == ApplicationTypes.NativeConfidential)
            {
                if (string.IsNullOrWhiteSpace(clientSecret))
                {
                    context.SetError("invalid_clientId", "Client secret should be sent.");
                    return;
                }
                else
                {
                    string hashedClientSecret = Helper.GetHash(clientSecret);
                    if (client.Secret != hashedClientSecret)
                    {
                        context.SetError("invalid_clientId", "Client secret is invalid.");
                        return;
                    }
                }
            }
            context.OwinContext.Set <string>("as:clientAllowedOrigin", client.AllowedOrigin);
            context.OwinContext.Set <string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
            context.OwinContext.Set <ApiClient>("oauth:client", client);
            context.Validated(clientId);

            //context.Validated();
            return;
        }
Example #10
0
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            try
            {
                string clientId;
                string clientSecret;

                if (context.TryGetBasicCredentials(out clientId, out clientSecret))
                {
                    PulseUserManager userManager = context.OwinContext.GetUserManager <PulseUserManager>();

                    IUnitOfWork unitOfWork = ResolverFactory.GetService <IUnitOfWork>();

                    try
                    {
                        var client = await unitOfWork.Clients.FindAll(c => c.ClientId.Equals(clientId)).FirstOrDefaultAsync();

                        if (client != null &&
                            userManager.PasswordHasher.VerifyHashedPassword(
                                client.Secret, clientSecret) == PasswordVerificationResult.Success)
                        {
                            context.OwinContext.Set("oauth:client", client);

                            string userName = context.Parameters.GetValues("username")[0];

                            var user = await userManager.FindByClientIdAndNameAsync(client.ClientId, userName);

                            if (userManager.IsInRole(user.Id, PulseIdentityRole.Kiosk))
                            {
                                context.Options.AccessTokenExpireTimeSpan = TimeSpan.FromDays(365);
                            }
                            else
                            {
                                context.Options.AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(client.TokenLifeTime);
                            }

                            context.Validated(clientId);
                        }
                        else
                        {
                            context.SetError("invalid_client", "Client credent``ials are invalid.");
                            context.Rejected();
                        }
                    }
                    catch
                    {
                        context.SetError("server_error");
                        context.Rejected();
                    }
                }
                else
                {
                    context.SetError(
                        "invalid_client",
                        "Client credentials could not be retrieved through the Authorization header.");

                    context.Rejected();
                }
            }
            catch (Exception ex)
            {
                context.SetError("ValidateClientAuthentication " + ex.Message);
            }
        }
Example #11
0
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            try
            {
                var username = context.Parameters["username"];
                var password = context.Parameters["password"];

                if (String.IsNullOrWhiteSpace(username) || String.IsNullOrWhiteSpace(password))
                {
                    context.SetError("Invalid credentials");
                    context.Rejected();
                    return;
                }

                if (username == password)//Only for testing purpose
                {
                    context.OwinContext.Set("ac:username", username);
                    context.OwinContext.Set("ac:customer_id", username);
                    context.OwinContext.Set("ac:email", username);
                    if (username.Equals("*****@*****.**"))
                    {
                        context.OwinContext.Set("ac:role", CustomRoles.User);
                        context.OwinContext.Set("ac:role_id", 3);
                    }
                    else if (username.Equals("*****@*****.**"))
                    {
                        context.OwinContext.Set("ac:role", CustomRoles.Researcher);
                        context.OwinContext.Set("ac:role_id", 2);
                    }
                    else if (username.Equals("*****@*****.**"))
                    {
                        context.OwinContext.Set("ac:role", CustomRoles.Admin);
                        context.OwinContext.Set("ac:role_id", 1);
                    }
                    else
                    {
                        context.OwinContext.Set("ac:role", CustomRoles.User);
                        context.OwinContext.Set("ac:role_id", 3);
                    }
                    context.Validated();
                    return;
                }

                var autoCareUser = await _personifyHelper.GetUserAsync(username, password);

                if (autoCareUser == null)
                {
                    context.SetError("Invalid credentials");
                    context.Rejected();
                    return;
                }

                context.OwinContext.Set("ac:username", autoCareUser.UserName);
                context.OwinContext.Set("ac:customer_id", autoCareUser.Id);
                context.OwinContext.Set("ac:email", autoCareUser.Email);
                context.OwinContext.Set("ac:customer_token", autoCareUser.CustomerToken);
                context.Validated();
            }
            catch
            {
                context.SetError("Server error");
                context.Rejected();
            }
        }
Example #12
0
        // NOTE: 클라이언트 측에 대한 검증 부분에 대한 내용 정리
        // 1. access_token, refres_token 에 대해서 쿠키에 구울 것인가, auth 쿠키에 구울 것인가? - 토큰정보에 대한 암호화에 대한 부분
        // 2. client_secret 에 대한 공유 - Javascript client 의 경우, 따로 저장하고 있지 못함.(노출이 쉬우므로...)
        // 3. refresh 정책 결정 - exp 전에 refresh 할 것인가? 아니면 요청마다 refresh 할 것인가? 그도 아니면 exp 남은 시간을 보고 refresh 할 것인가?
        // 4. refresh token 자체가 golden ticket 이 될 수 있으므로, 이 부분에 대한 보관 방법에 대한 고민이 필요하다. - 메모리에 휘발성? 아니면 auth_cookie
        // 5. 보관하고 있더라도, 암호화해서 해쉬화해서 가지고 있을 것인가?
        // 6. 한 클라이언트만 로그인 가능하도록 하기 위해서는 클라이언트 머신키를 보유하는 것도 한가지 방법이다. - 하지만, 이 또한 fraud 가능성이 있다.

        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            string clientId     = string.Empty;
            string clientSecret = string.Empty;

            if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
            {
                context.TryGetFormCredentials(out clientId, out clientSecret);
            }

            if (string.IsNullOrWhiteSpace(context.ClientId))
            {
                context.Rejected();
                context.SetError("invalid_clientId", "ClientId should be sent.");

                return(Task.FromResult <object>(null));
            }

            Client client = context.OwinContext.Get <RefreshTokenManager>().FindClient(context.ClientId);

            if (client == null)
            {
                context.Rejected();
                context.SetError("invalid_clientId", $"Client '{context.ClientId}' is not registered in the system.");

                return(Task.FromResult <object>(null));
            }

            // Javascript client 가 아닐 경우, client secret 체크
            if (client.ApplicationType == ApplicationType.NativeConfidential)
            {
                if (string.IsNullOrWhiteSpace(clientSecret))
                {
                    context.Rejected();
                    context.SetError("invalid_clientId", "Client secret should be sent.");

                    return(Task.FromResult <object>(null));
                }
                else
                {
                    // NOTE: 암호화된 상태로 넘기도록 요청할 경우 이 부분의 로직을 변경
                    // https 가 아니면 중간에 client secret 에 대한 하이잭이 가능
                    if (clientSecret != client.Secret)
                    {
                        context.Rejected();
                        context.SetError("invalid_clientId", "Client secret is invalid.");

                        return(Task.FromResult <object>(null));
                    }
                }
            }

            if (!client.Active)
            {
                context.Rejected();
                context.SetError("invalid_clientId", "Client is inactive.");

                return(Task.FromResult <object>(null));
            }

            context.OwinContext.Set(Startup.ClientAllowedOriginPropertyName, client.AllowedOrigin);
            context.OwinContext.Set(Startup.ClientRefreshTokenLifeTimePropertyName, client.RefreshTokenLifeTime.ToString());

            context.Validated();

            return(Task.FromResult <object>(null));
        }