GitHub.Models.RepositoryHost.LogIn C# (CSharp) Method

LogIn() public method

public LogIn ( string usernameOrEmail, string password ) : IObservable
usernameOrEmail string
password string
return IObservable
        public IObservable<AuthenticationResult> LogIn(string usernameOrEmail, string password)
        {
            Guard.ArgumentNotEmptyString(usernameOrEmail, nameof(usernameOrEmail));
            Guard.ArgumentNotEmptyString(password, nameof(password));

            // If we need to retry on fallback, we'll store the 2FA token 
            // from the first request to re-use:
            string authenticationCode = null;

            // We need to intercept the 2FA handler to get the token:
            var interceptingTwoFactorChallengeHandler =
                new Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>>(ex =>
                    twoFactorChallengeHandler.HandleTwoFactorException(ex)
                    .Do(twoFactorChallengeResult =>
                        authenticationCode = twoFactorChallengeResult.AuthenticationCode));

            // Keep the function to save the authorization token here because it's used
            // in multiple places in the chain below:
            var saveAuthorizationToken = new Func<ApplicationAuthorization, IObservable<Unit>>(authorization =>
            {
                var token = authorization?.Token;
                if (string.IsNullOrWhiteSpace(token))
                    return Observable.Return(Unit.Default);

                return loginCache.SaveLogin(usernameOrEmail, token, Address)
                    .ObserveOn(RxApp.MainThreadScheduler);
            });

            // Start be saving the username and password, as they will be used for older versions of Enterprise
            // that don't support authorization tokens, and for the API client to use until an authorization
            // token has been created and acquired:
            return loginCache.SaveLogin(usernameOrEmail, password, Address)
                .ObserveOn(RxApp.MainThreadScheduler)
                // Try to get an authorization token, save it, then get the user to log in:
                .SelectMany(fingerprint => ApiClient.GetOrCreateApplicationAuthenticationCode(interceptingTwoFactorChallengeHandler))
                .SelectMany(saveAuthorizationToken)
                .SelectMany(_ => GetUserFromApi())
                .Catch<UserAndScopes, ApiException>(firstTryEx =>
                {
                    var exception = firstTryEx as AuthorizationException;
                    if (isEnterprise
                        && exception != null
                        && exception.Message == "Bad credentials")
                    {
                        return Observable.Throw<UserAndScopes>(exception);
                    }

                    // If the Enterprise host doesn't support the write:public_key scope, it'll return a 422.
                    // EXCEPT, there's a bug where it doesn't, and instead creates a bad token, and in 
                    // that case we'd get a 401 here from the GetUser invocation. So to be safe (and consistent
                    // with the Mac app), we'll just retry after any API error for Enterprise hosts:
                    if (isEnterprise && !(firstTryEx is TwoFactorChallengeFailedException))
                    {
                        // Because we potentially have a bad authorization token due to the Enterprise bug,
                        // we need to reset to using username and password authentication:
                        return loginCache.SaveLogin(usernameOrEmail, password, Address)
                            .ObserveOn(RxApp.MainThreadScheduler)
                            .SelectMany(_ =>
                            {
                                // Retry with the old scopes. If we have a stashed 2FA token, we use it:
                                if (authenticationCode != null)
                                {
                                    return ApiClient.GetOrCreateApplicationAuthenticationCode(
                                        interceptingTwoFactorChallengeHandler,
                                        authenticationCode,
                                        useOldScopes: true,
                                        useFingerprint: false);
                                }

                                // Otherwise, we use the default handler:
                                return ApiClient.GetOrCreateApplicationAuthenticationCode(
                                    interceptingTwoFactorChallengeHandler,
                                    useOldScopes: true,
                                    useFingerprint: false);
                            })
                            // Then save the authorization token (if there is one) and get the user:
                            .SelectMany(saveAuthorizationToken)
                            .SelectMany(_ => GetUserFromApi());
                    }

                    return Observable.Throw<UserAndScopes>(firstTryEx);
                })
                .Catch<UserAndScopes, ApiException>(retryEx =>
                {
                    // Older Enterprise hosts either don't have the API end-point to PUT an authorization, or they
                    // return 422 because they haven't white-listed our client ID. In that case, we just ignore
                    // the failure, using basic authentication (with username and password) instead of trying
                    // to get an authorization token.
                    // Since enterprise 2.1 and https://github.com/github/github/pull/36669 the API returns 403
                    // instead of 404 to signal that it's not allowed. In the name of backwards compatibility we 
                    // test for both 404 (NotFoundException) and 403 (ForbiddenException) here.
                    if (isEnterprise && (retryEx is NotFoundException || retryEx is ForbiddenException || retryEx.StatusCode == (HttpStatusCode)422))
                        return GetUserFromApi();

                    // Other errors are "real" so we pass them along:
                    return Observable.Throw<UserAndScopes>(retryEx);
                })
                .ObserveOn(RxApp.MainThreadScheduler)
                .Catch<UserAndScopes, Exception>(ex =>
                {
                    // If we get here, we have an actual login failure:
                    if (ex is TwoFactorChallengeFailedException)
                    {
                        return Observable.Return(unverifiedUser);
                    }
                    if (ex is AuthorizationException)
                    {
                        return Observable.Return(default(UserAndScopes));
                    }
                    return Observable.Throw<UserAndScopes>(ex);
                })
                .SelectMany(LoginWithApiUser)
                .PublishAsync();
        }

Usage Example

コード例 #1
0
        public async Task LogsTheUserInSuccessfullyAndCachesRelevantInfo()
        {
            var apiClient = Substitute.For<IApiClient>();
            apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress);
            apiClient.GetOrCreateApplicationAuthenticationCode(
                Args.TwoFactorChallengCallback, Args.String, Args.Boolean)
                .Returns(Observable.Return(new ApplicationAuthorization("S3CR3TS")));
            apiClient.GetUser().Returns(Observable.Return(CreateUserAndScopes("baymax")));
            var hostCache = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, hostCache, Substitute.For<IAvatarProvider>());
            var loginCache = new TestLoginCache();
            var usage = Substitute.For<IUsageTracker>();
            var host = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For<ITwoFactorChallengeHandler>(), usage);

            var result = await host.LogIn("baymax", "aPassword");

            Assert.Equal(AuthenticationResult.Success, result);
            var user = await hostCache.GetObject<AccountCacheItem>("user");
            Assert.NotNull(user);
            Assert.Equal("baymax", user.Login);
            var loginInfo = await loginCache.GetLoginAsync(HostAddress.GitHubDotComHostAddress);
            Assert.Equal("baymax", loginInfo.UserName);
            Assert.Equal("S3CR3TS", loginInfo.Password);
            Assert.True(host.IsLoggedIn);
        }
All Usage Examples Of GitHub.Models.RepositoryHost::LogIn