Ejemplo n.º 1
0
        private async Task<AuthenticateResult> GetAppropriateAuthenticationResult(ExternalIdentity externalIdentity)
        {
            var account = await _userRepository.GetUserForExternalProviderAsync(externalIdentity.Provider,
                externalIdentity.ProviderId);

            if (account != null)
            {
                return CreateSuccesFullAuthentication(externalIdentity, account);
            }

            var emailClaim = externalIdentity.Claims.FirstOrDefault(c => c.Type == "email");
            if (emailClaim == null)
            {
                return new AuthenticateResult("An email is claim required to authenticate.");
            }

            var userWithMatchingEmailClaim = await _userRepository.GetUserByEmailAsync(emailClaim.Value);

            if (userWithMatchingEmailClaim == null && externalIdentity.Provider == "windows")
            {
                return AskWindowsAuthenticatedUserForAdditionalInfo(externalIdentity);
            }

            if (userWithMatchingEmailClaim == null)
            {
                return CreateNewUserAndAuthenticate(externalIdentity);
            }

            return await AuthenticateExistingUserWithNewExternalProvider(
                externalIdentity, userWithMatchingEmailClaim);
        }
Ejemplo n.º 2
0
        private AuthenticateResult CreateNewUserAndAuthenticate(ExternalIdentity externalIdentity)
        {
            var newUser = ConfigureNewUser(externalIdentity);

            _userRepository.SaveUser(newUser);

            return CreateSuccesFullAuthentication(externalIdentity, newUser);
        }
Ejemplo n.º 3
0
        private async Task<AuthenticateResult> AuthenticateExistingUserWithNewExternalProvider(
            ExternalIdentity externalIdentity,
            User userWithMatchingEmailClaim)
        {
            await _userRepository.AddUserLoginAsync(
                userWithMatchingEmailClaim.Subject,
                externalIdentity.Provider,
                externalIdentity.ProviderId);

            return CreateSuccesFullAuthentication(externalIdentity, userWithMatchingEmailClaim);
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Initializes a new instance of the <see cref="AuthenticateResult" /> class. This
        /// version of the constructor indicates a partial login (with a redirect) without
        /// knowledge of the subject claim.
        /// </summary>
        /// <param name="redirectPath">The redirect path. This should be relative to the
        /// current web server. The <c>"~/"</c> prefix is supported to allow application-relative
        /// paths to be used (e.g. "~/path").
        /// </param>
        /// <param name="externalId">The external identifier that represents the external identity
        /// provider the partial login is created from. This will be re-presented to correlate the request
        /// when the user resumes from the redirect.</param>
        /// <exception cref="System.ArgumentNullException">
        /// redirectPath
        /// or
        /// externalId
        /// </exception>
        /// <exception cref="System.ArgumentException">redirectPath must start with / or ~/</exception>
        public AuthenticateResult(string redirectPath, ExternalIdentity externalId)
        {
            if (redirectPath.IsMissing())
            {
                throw new ArgumentNullException("redirectPath");
            }
            if (!redirectPath.StartsWith("~/") && !redirectPath.StartsWith("/"))
            {
                throw new ArgumentException("redirectPath must start with / or ~/");
            }
            if (externalId == null)
            {
                throw new ArgumentNullException("externalId");
            }

            this.PartialSignInRedirectPath = redirectPath;

            var id = new ClaimsIdentity(externalId.Claims, Constants.PartialSignInAuthenticationType);

            // we're keeping the external provider info for the partial signin so we can re-execute AuthenticateExternalAsync
            // once the user is re-directed back into identityserver from the external redirect
            id.AddClaim(new Claim(Constants.ClaimTypes.ExternalProviderUserId, externalId.ProviderId, ClaimValueTypes.String, externalId.Provider));
            User = new ClaimsPrincipal(id);
        }
Ejemplo n.º 5
0
        private async Task<AuthenticateResult> AddExternalUserAsync(HttpClient client, ExternalIdentity identity)
        {
            using (var response = await client.PostAsync($"/api/users", new StringContent($"{{ id: \"{identity.ProviderId}\", password: \"password\" }}", Encoding.Unicode, "text/json")))
            {
                if (!response.IsSuccessStatusCode)
                {
                    return new AuthenticateResult($"Could not create new external user. Error code: {response.StatusCode}");
                }
            }

            return new AuthenticateResult(identity.ProviderId, GetDisplayName(identity.Claims) ?? identity.ProviderId, identity.Claims);
        }
        public async Task<IHttpActionResult> ResumeLoginFromRedirect(string resume)
        {
            Logger.Info("Callback requested to resume login from partial login");

            if (resume.IsMissing())
            {
                Logger.Error("no resumeId passed");
                return RenderErrorPage();
            }

            if (resume.Length > MaxSignInMessageLength)
            {
                Logger.Error("resumeId length longer than allowed length");
                return RenderErrorPage();
            }

            var user = await context.GetIdentityFromPartialSignIn();
            if (user == null)
            {
                Logger.Error("no identity from partial login");
                return RenderErrorPage();
            }

            var type = GetClaimTypeForResumeId(resume);
            var resumeClaim = user.FindFirst(type);
            if (resumeClaim == null)
            {
                Logger.Error("no claim matching resumeId");
                return RenderErrorPage();
            }

            var signInId = resumeClaim.Value;
            if (signInId.IsMissing())
            {
                Logger.Error("No signin id found in resume claim");
                return RenderErrorPage();
            }

            var signInMessage = signInMessageCookie.Read(signInId);
            if (signInMessage == null)
            {
                Logger.Error("No cookie matching signin id found");
                return RenderErrorPage();
            }

            AuthenticateResult result = null;

            // determine which return path the user is taking -- are they coming from
            // a ExternalProvider partial logon, or not
            var externalProviderClaim = user.FindFirst(Constants.ClaimTypes.ExternalProviderUserId);

            // cleanup the claims from the partial login
            if (user.HasClaim(c => c.Type == Constants.ClaimTypes.PartialLoginRestartUrl))
            {
                user.RemoveClaim(user.FindFirst(Constants.ClaimTypes.PartialLoginRestartUrl));
            }
            if (user.HasClaim(c => c.Type == Constants.ClaimTypes.PartialLoginReturnUrl))
            {
                user.RemoveClaim(user.FindFirst(Constants.ClaimTypes.PartialLoginReturnUrl));
            }
            if (user.HasClaim(c => c.Type == Constants.ClaimTypes.ExternalProviderUserId))
            {
                user.RemoveClaim(user.FindFirst(Constants.ClaimTypes.ExternalProviderUserId));
            }
            if (user.HasClaim(c => c.Type == GetClaimTypeForResumeId(resume)))
            {
                user.RemoveClaim(user.FindFirst(GetClaimTypeForResumeId(resume)));
            }

            if (externalProviderClaim != null)
            {
                Logger.Info("using ExternalProviderUserId to call AuthenticateExternalAsync");

                var provider = externalProviderClaim.Issuer;
                var providerId = externalProviderClaim.Value;
                var externalIdentity = new ExternalIdentity
                {
                    Provider = provider,
                    ProviderId = providerId,
                    Claims = user.Claims
                };

                Logger.InfoFormat("external user provider: {0}, provider ID: {1}", externalIdentity.Provider, externalIdentity.ProviderId);

                var externalContext = new ExternalAuthenticationContext
                {
                    ExternalIdentity = externalIdentity,
                    SignInMessage = signInMessage
                };

                await userService.AuthenticateExternalAsync(externalContext);

                result = externalContext.AuthenticateResult;
                if (result == null)
                {
                    Logger.Warn("user service failed to authenticate external identity");

                    var msg = localizationService.GetMessage(MessageIds.NoMatchingExternalAccount);
                    await eventService.RaiseExternalLoginFailureEventAsync(externalIdentity, signInId, signInMessage, msg);

                    return await RenderLoginPage(signInMessage, signInId, msg);
                }

                if (result.IsError)
                {
                    Logger.WarnFormat("user service returned error message: {0}", result.ErrorMessage);

                    await eventService.RaiseExternalLoginFailureEventAsync(externalIdentity, signInId, signInMessage, result.ErrorMessage);

                    return await RenderLoginPage(signInMessage, signInId, result.ErrorMessage);
                }

                Logger.Info("External identity successfully validated by user service");

                await eventService.RaiseExternalLoginSuccessEventAsync(externalIdentity, signInId, signInMessage, result);
            }
            else
            {
                // check to see if the resultant user has all the claim types needed to login
                if (!Constants.AuthenticateResultClaimTypes.All(claimType => user.HasClaim(c => c.Type == claimType)))
                {
                    Logger.Error("Missing AuthenticateResultClaimTypes -- rendering error page");
                    return RenderErrorPage();
                }

                // this is a normal partial login continuation
                Logger.Info("Partial login resume success -- logging user in");

                result = new AuthenticateResult(new ClaimsPrincipal(user));

                await eventService.RaisePartialLoginCompleteEventAsync(result.User.Identities.First(), signInId, signInMessage);
            }

            return await SignInAndRedirectAsync(signInMessage, signInId, result);
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="AuthenticateResult" /> class. This
        /// version of the constructor indicates a partial login (with a redirect) without
        /// knowledge of the subject claim.
        /// </summary>
        /// <param name="redirectPath">The redirect path. This should be relative to the 
        /// current web server. The <c>"~/"</c> prefix is supported to allow application-relative
        /// paths to be used (e.g. "~/path").
        /// </param>
        /// <param name="externalId">The external identifier that represents the external identity
        /// provider the partial login is created from. This will be re-presented to correlate the request
        /// when the user resumes from the redirect.</param>
        /// <exception cref="System.ArgumentNullException">
        /// redirectPath
        /// or
        /// externalId
        /// </exception>
        /// <exception cref="System.ArgumentException">redirectPath must start with / or ~/</exception>
        public AuthenticateResult(string redirectPath, ExternalIdentity externalId)
        {
            if (redirectPath.IsMissing()) throw new ArgumentNullException("redirectPath");
            if (!redirectPath.StartsWith("~/") && !redirectPath.StartsWith("/"))
            {
                throw new ArgumentException("redirectPath must start with / or ~/");
            }
            if (externalId == null) throw new ArgumentNullException("externalId");

            this.PartialSignInRedirectPath = redirectPath;

            var id = new ClaimsIdentity(externalId.Claims, Constants.PartialSignInAuthenticationType);
            // we're keeping the external provider info for the partial signin so we can re-execute AuthenticateExternalAsync
            // once the user is re-directed back into identityserver from the external redirect
            id.AddClaim(new Claim(Constants.ClaimTypes.ExternalProviderUserId, externalId.ProviderId, ClaimValueTypes.String, externalId.Provider));
            User = new ClaimsPrincipal(id);
        }
Ejemplo n.º 8
0
        private static User ConfigureNewUser(ExternalIdentity externalIdentity)
        {
            var newUser = new User
            {
                Subject = Guid.NewGuid().ToString(),
                IsActive = true
            };

            var userLogin = new UserLogin
            {
                Subject = newUser.Subject,
                LoginProvider = externalIdentity.Provider,
                ProviderKey = externalIdentity.ProviderId
            };
            newUser.UserLogins.Add(userLogin);

            GetProfileClaimsFromIdentity(externalIdentity, newUser)
                .Union(CreateBasicPrivilegeForNewUser(newUser))
                .ToList()
                .ForEach(newUser.UserClaims.Add);

            return newUser;
        }
Ejemplo n.º 9
0
 private static AuthenticateResult CreateSuccesFullAuthentication(ExternalIdentity externalIdentity,
     User authenticatedUser)
 {
     return new AuthenticateResult(
         authenticatedUser.Subject,
         authenticatedUser.UserClaims.First(c => c.ClaimType == Constants.ClaimTypes.GivenName).ClaimValue,
         authenticatedUser.UserClaims.Select(uc => new Claim(uc.ClaimType, uc.ClaimValue)),
         authenticationMethod: Constants.AuthenticationMethods.External,
         identityProvider: externalIdentity.Provider);
 }
Ejemplo n.º 10
0
 private static AuthenticateResult AskWindowsAuthenticatedUserForAdditionalInfo(ExternalIdentity externalIdentity)
 {
     return new AuthenticateResult("~/completeadditionalinformation", externalIdentity);
 }
Ejemplo n.º 11
0
        private static IEnumerable<UserClaim> GetProfileClaimsFromIdentity(ExternalIdentity externalIdentity,
            User newUser)
        {
            return externalIdentity
                .Claims.Where(c =>
                    c.Type.ToLowerInvariant() == Constants.ClaimTypes.GivenName ||
                    c.Type.ToLowerInvariant() == Constants.ClaimTypes.FamilyName ||
                    c.Type.ToLowerInvariant() == Constants.ClaimTypes.Email)

                .Select(c => new UserClaim
                {
                    Id = Guid.NewGuid().ToString(),
                    Subject = newUser.Subject,
                    ClaimType = c.Type.ToLowerInvariant(),
                    ClaimValue = c.Value
                });
        }