< Summary

Information
Class: OzonOrdersWeb.Areas.Identity.Pages.Account.ExternalLoginModel
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 115
Coverable lines: 115
Total lines: 210
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 44
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
get_Input()100%210%
get_ProviderDisplayName()100%210%
get_ReturnUrl()100%210%
get_ErrorMessage()100%210%
get_Email()100%210%
OnPost(...)100%210%
OnGetCallbackAsync()0%812280%
LogAuthenticationInfo(...)0%620%
LogAuthenticationWarning(...)0%620%
UpdateGoogleProfileAsync()0%702260%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3#nullable disable
 4
 5using System.ComponentModel.DataAnnotations;
 6using System.Security.Claims;
 7using Microsoft.AspNetCore.Authentication;
 8using Microsoft.AspNetCore.Identity;
 9using Microsoft.AspNetCore.Mvc;
 10using Microsoft.AspNetCore.Mvc.RazorPages;
 11using OzonRepositories.Context.Identity;
 12
 13namespace OzonOrdersWeb.Areas.Identity.Pages.Account;
 14
 15public class ExternalLoginModel : PageModel
 16{
 17    private readonly SignInManager<CustomIdentityUser> _signInManager;
 18    private readonly UserManager<CustomIdentityUser> _userManager;
 19    private readonly ILogger<ExternalLoginModel> _logger;
 20
 021    public ExternalLoginModel(
 022        SignInManager<CustomIdentityUser> signInManager,
 023        UserManager<CustomIdentityUser> userManager,
 024        ILogger<ExternalLoginModel> logger)
 25    {
 026        _signInManager = signInManager;
 027        _userManager = userManager;
 028        _logger = logger;
 029    }
 30
 31    [BindProperty]
 032    public InputModel Input { get; set; }
 33
 034    public string ProviderDisplayName { get; set; }
 035    public string ReturnUrl { get; set; }
 36
 37    [TempData]
 038    public string ErrorMessage { get; set; }
 39
 40    public class InputModel
 41    {
 42        [Required]
 43        [EmailAddress]
 044        public string Email { get; set; }
 45    }
 46
 47    public IActionResult OnPost(string provider, string returnUrl = null)
 48    {
 049        var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
 050        var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
 051        return new ChallengeResult(provider, properties);
 52    }
 53
 54    public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
 55    {
 056        returnUrl ??= "/Studio2/OrdersGrid";
 057        if (remoteError != null)
 58        {
 059            LogAuthenticationWarning("ExternalLoginProviderError", remoteError);
 060            ErrorMessage = $"Ошибка внешнего провайдера: {remoteError}";
 061            return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
 62        }
 63
 064        var info = await _signInManager.GetExternalLoginInfoAsync();
 065        if (info == null)
 66        {
 067            LogAuthenticationWarning("ExternalLoginInfoMissing");
 068            ErrorMessage = "Не удалось получить данные внешнего входа.";
 069            return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
 70        }
 71
 072        var signInResult = await _signInManager.ExternalLoginSignInAsync(
 073            info.LoginProvider,
 074            info.ProviderKey,
 075            isPersistent: false,
 076            bypassTwoFactor: true);
 77
 078        if (signInResult.Succeeded)
 79        {
 080            var linkedUser = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
 081            if (linkedUser == null)
 82            {
 083                LogAuthenticationWarning("ExternalLoginUserMissing", provider: info.LoginProvider);
 084                await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
 085                ErrorMessage = "Вход запрещен: пользователь не найден.";
 086                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
 87            }
 88
 089            await UpdateGoogleProfileAsync(linkedUser, info);
 090            if (!linkedUser.EmailConfirmed || !linkedUser.IsAdminApproved)
 91            {
 092                LogAuthenticationInfo("ExternalLoginNotApproved", linkedUser.Id, info.LoginProvider);
 093                await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
 094                ErrorMessage = "Вход запрещен: требуется подтверждение email и одобрение администратором.";
 095                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
 96            }
 97
 098            _logger.LogInformation("Пользователь вошел через {LoginProvider}.", info.LoginProvider);
 099            return LocalRedirect(returnUrl);
 100        }
 101
 0102        if (signInResult.IsLockedOut)
 103        {
 0104            LogAuthenticationWarning("ExternalLoginLockedOut", provider: info.LoginProvider);
 0105            return RedirectToPage("./Lockout");
 106        }
 107
 0108        var email = info.Principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
 0109        if (string.IsNullOrWhiteSpace(email))
 110        {
 0111            LogAuthenticationInfo("ExternalLoginEmailMissing", provider: info.LoginProvider);
 0112            ErrorMessage = "Google не передал email. Вход невозможен.";
 0113            return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
 114        }
 115
 0116        var user = await _userManager.FindByEmailAsync(email);
 0117        if (user == null)
 118        {
 0119            LogAuthenticationInfo("ExternalLoginUserNotFound", detail: email, provider: info.LoginProvider);
 0120            ErrorMessage = "Пользователь с таким email не найден. Обратитесь к администратору.";
 0121            return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
 122        }
 123
 0124        var addLoginResult = await _userManager.AddLoginAsync(user, info);
 0125        if (!addLoginResult.Succeeded)
 126        {
 0127            LogAuthenticationWarning("ExternalLoginLinkFailed", user.Id, info.LoginProvider);
 0128            ErrorMessage = "Не удалось привязать Google-аккаунт к пользователю.";
 0129            return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
 130        }
 131
 0132        await UpdateGoogleProfileAsync(user, info);
 0133        if (!user.EmailConfirmed || !user.IsAdminApproved)
 134        {
 0135            LogAuthenticationInfo("ExternalLoginNotApproved", user.Id, info.LoginProvider);
 0136            ErrorMessage = "Вход запрещен: требуется подтверждение email и одобрение администратором.";
 0137            return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
 138        }
 139
 0140        await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
 0141        _logger.LogInformation("Google-аккаунт привязан и вход выполнен для пользователя {UserId}.", user.Id);
 0142        return LocalRedirect(returnUrl);
 0143    }
 144
 145    private void LogAuthenticationInfo(string eventKind, string detail = null, string provider = null)
 146    {
 0147        using var scope = _logger.BeginScope(new Dictionary<string, object>
 0148        {
 0149            ["EventKind"] = eventKind,
 0150            ["EventCategory"] = "auth",
 0151            ["RequestPath"] = HttpContext.Request.Path.Value,
 0152            ["TraceId"] = HttpContext.TraceIdentifier,
 0153            ["LoginProvider"] = provider,
 0154            ["RemoteIp"] = HttpContext.Connection.RemoteIpAddress?.ToString()
 0155        });
 0156        _logger.LogInformation("Внешний вход {EventKind}: {Detail}", eventKind, detail);
 0157    }
 158
 159    private void LogAuthenticationWarning(string eventKind, string detail = null, string provider = null)
 160    {
 0161        using var scope = _logger.BeginScope(new Dictionary<string, object>
 0162        {
 0163            ["EventKind"] = eventKind,
 0164            ["EventCategory"] = "auth",
 0165            ["RequestPath"] = HttpContext.Request.Path.Value,
 0166            ["TraceId"] = HttpContext.TraceIdentifier,
 0167            ["LoginProvider"] = provider,
 0168            ["RemoteIp"] = HttpContext.Connection.RemoteIpAddress?.ToString()
 0169        });
 0170        _logger.LogWarning("Ошибка внешнего входа {EventKind}: {Detail}", eventKind, detail);
 0171    }
 172
 173    private async Task UpdateGoogleProfileAsync(CustomIdentityUser user, ExternalLoginInfo info)
 174    {
 0175        if (!string.Equals(info.LoginProvider, "Google", StringComparison.OrdinalIgnoreCase))
 176        {
 0177            return;
 178        }
 179
 0180        var principal = info.Principal;
 0181        var displayName = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
 0182        if (string.IsNullOrWhiteSpace(displayName))
 183        {
 0184            var givenName = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value;
 0185            var surname = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value;
 0186            displayName = $"{givenName} {surname}".Trim();
 187        }
 188
 0189        var avatarUrl = principal.Claims.FirstOrDefault(c => c.Type == "picture")?.Value
 0190            ?? principal.Claims.FirstOrDefault(c => c.Type == "urn:google:picture")?.Value;
 191
 0192        var changed = false;
 0193        if (!string.IsNullOrWhiteSpace(displayName) && !string.Equals(user.GoogleDisplayName, displayName, StringCompari
 194        {
 0195            user.GoogleDisplayName = displayName;
 0196            changed = true;
 197        }
 198
 0199        if (!string.IsNullOrWhiteSpace(avatarUrl) && !string.Equals(user.GoogleAvatarUrl, avatarUrl, StringComparison.Or
 200        {
 0201            user.GoogleAvatarUrl = avatarUrl;
 0202            changed = true;
 203        }
 204
 0205        if (changed)
 206        {
 0207            await _userManager.UpdateAsync(user);
 208        }
 0209    }
 210}