Base solution for your next web application
Ends in:
01 DAYS
01 HRS
01 MIN
01 SEC

Activities of "BobIngham"

Answer

Hi @ryancyq, yes ExpirationHeartbeat() is still audited if comment out [AbpAuthorize] and [DisableAuditing] .

Answer

@Aaron, I stand corrected. TokenAuthController.Authenticate is already audited. I am unable to upload my AppTokenAuthController so here it is in it's entirety. Interestingly the method ExpirationHeartbeat is fully audited (and has to be turned off) but the method AppAuthenticate is not audited. Could you take a look and suggest anything?

using Abp;
using Abp.AspNetZeroCore.Web.Authentication.External;
using Abp.Auditing;
using Abp.Authorization;
using Abp.Authorization.Users;
using Abp.Configuration;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.Extensions;
using Abp.MultiTenancy;
using Abp.Net.Mail;
using Abp.Runtime.Caching;
using Abp.Runtime.Security;
using Abp.Timing;
using Abp.UI;
using Abp.Web.Models;
using Abp.Zero.Configuration;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Nuagecare.App.Services;
using Nuagecare.Authentication.TwoFactor.Google;
using Nuagecare.Authorization;
using Nuagecare.Authorization.Devices;
using Nuagecare.Authorization.Impersonation;
using Nuagecare.Authorization.Roles;
using Nuagecare.Authorization.Users;
using Nuagecare.Configuration;
using Nuagecare.Configuration.Tenants;
using Nuagecare.DataProvider.Dtos;
using Nuagecare.Identity;
using Nuagecare.MultiTenancy;
using Nuagecare.Notifications;
using Nuagecare.Web.Authentication.JwtBearer;
using Nuagecare.Web.Authentication.TwoFactor;
using Nuagecare.Web.Models.TokenAuth;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace Nuagecare.Web.Controllers
{
    [Route("api/[controller]/[action]")]
    public class AppTokenAuthController : NuagecareControllerBase
    {
        private const string UserIdentifierClaimType = "http://aspnetzero.com/claims/useridentifier";
        private readonly LogInManager _logInManager;
        private readonly ITenantCache _tenantCache;
        private readonly AbpLoginResultTypeHelper _abpLoginResultTypeHelper;
        private readonly TokenAuthConfiguration _configuration;
        private readonly UserManager _userManager;
        private readonly ICacheManager _cacheManager;
        private readonly IOptions<JwtBearerOptions> _jwtOptions;
        private readonly IExternalAuthConfiguration _externalAuthConfiguration;
        private readonly IExternalAuthManager _externalAuthManager;
        private readonly UserRegistrationManager _userRegistrationManager;
        private readonly IImpersonationManager _impersonationManager;
        private readonly IUserLinkManager _userLinkManager;
        private readonly IAppNotifier _appNotifier;
        private readonly ISmsSender _smsSender;
        private readonly IEmailSender _emailSender;
        private readonly IdentityOptions _identityOptions;
        private readonly GoogleAuthenticatorProvider _googleAuthenticatorProvider;

        //Nuage settings
        private readonly TenantManager _tenantManager;
        private readonly IUnitOfWorkManager _unitOfWorkManager;
        private readonly DataProviderAPIProvider _dataProviderAPIProvider;
        private readonly IUserPolicy _userPolicy;
        private readonly IPasswordHasher<User> _passwordHasher;
        private readonly RoleManager _roleManager;
        private readonly TenantSettingsAppService _tenantSettingsAppService;
        private readonly IRepository<NcDeviceAndUserLoginAudit> _deviceAndUserLoginAuditRepository;

        public AppTokenAuthController(
            LogInManager logInManager,
            ITenantCache tenantCache,
            AbpLoginResultTypeHelper abpLoginResultTypeHelper,
            TokenAuthConfiguration configuration,
            UserManager userManager,
            ICacheManager cacheManager,
            IOptions<JwtBearerOptions> jwtOptions,
            IExternalAuthConfiguration externalAuthConfiguration,
            IExternalAuthManager externalAuthManager,
            UserRegistrationManager userRegistrationManager,
            IImpersonationManager impersonationManager,
            IUserLinkManager userLinkManager,
            IAppNotifier appNotifier,
            ISmsSender smsSender,
            IEmailSender emailSender,
            IOptions<IdentityOptions> identityOptions,
            GoogleAuthenticatorProvider googleAuthenticatorProvider,
            TenantManager tenantManager,
            IUnitOfWorkManager unitOfWorkManager,
            DataProviderAPIProvider dataProviderAPIProvider,
            IUserPolicy userPolicy,
            IPasswordHasher<User> passwordHasher,
            RoleManager roleManager,
            TenantSettingsAppService tenantSettingsAppService,
            IRepository<NcDeviceAndUserLoginAudit> deviceAndUserLoginAuditRepository)
        {
            _logInManager = logInManager;
            _tenantCache = tenantCache;
            _abpLoginResultTypeHelper = abpLoginResultTypeHelper;
            _configuration = configuration;
            _userManager = userManager;
            _cacheManager = cacheManager;
            _jwtOptions = jwtOptions;
            _externalAuthConfiguration = externalAuthConfiguration;
            _externalAuthManager = externalAuthManager;
            _userRegistrationManager = userRegistrationManager;
            _impersonationManager = impersonationManager;
            _userLinkManager = userLinkManager;
            _appNotifier = appNotifier;
            _smsSender = smsSender;
            _emailSender = emailSender;
            _googleAuthenticatorProvider = googleAuthenticatorProvider;
            _identityOptions = identityOptions.Value;
            _tenantManager = tenantManager;
            _unitOfWorkManager = unitOfWorkManager;
            _dataProviderAPIProvider = dataProviderAPIProvider;
            _userPolicy = userPolicy;
            _passwordHasher = passwordHasher;
            _roleManager = roleManager;
            _tenantSettingsAppService = tenantSettingsAppService;
            _deviceAndUserLoginAuditRepository = deviceAndUserLoginAuditRepository;
        }

        [HttpPost]
        [AbpAuthorize]
        [DisableAuditing]
        public string ExpirationHeartbeat()
        {
            return "Ok";
        }

        [HttpPost]
        [DontWrapResult(WrapOnError = true)]
        [Audited]
        public async Task<AppAuthenticateResultModel> AppAuthenticate([FromBody] AppAuthenticateModel model)
        {
            // Nuage stuff - null tenant value
            if (model.TenantId == 0)
            {
                throw new UserFriendlyException(L("PleaseRegisterThisDeviceBeforeSigningIn."));
            }

            //if the user exists in the data provider but not in Zero we need to add a new user....
            using (_unitOfWorkManager.Current.SetTenantId(model.TenantId))
            {
                //see https://forum.aspnetboilerplate.com/viewtopic.php?f=5&t=10044&p=23035&hilit=set+abpsession&sid=99db30a39413f544f1b2bfeb49ff4d18#p23035
                AbpSession.Use(model.TenantId, null);
                var tenantDataProvider = await SettingManager.GetSettingValueAsync(NuageSettings.NuageTenantManagement.DataProvider);

                var DataProviderApiUrl = await SettingManager.GetSettingValueAsync(NuageSettings.NuageTenantManagement.DataProviderApiUrl);
                if (String.IsNullOrEmpty(DataProviderApiUrl))
                {
                    throw new UserFriendlyException(L("InvalidDataProviderApiUrl"));
                }

                if (tenantDataProvider != "Nuage")
                {
                    var zeroUser = await _userManager.FindByNameOrEmailAsync(model.UserNameOrEmailAddress);
                    if (zeroUser == null)
                    {
                        await _userPolicy.CheckMaxUserCountAsync(model.TenantId);
                        var DataProviderUser = await _dataProviderAPIProvider.DataProviderLoginAndReturnUser(model.UserNameOrEmailAddress, model.Password);
                        if (DataProviderUser != null)
                        {
                            await CreateDataProviderUser(model, DataProviderUser);
                        }
                        else
                        {
                            throw new UserFriendlyException(L("InvalidUserNameOrPassword"));
                        }
                    }
                }
            }

            var loginResult = await GetLoginResultAsync(
              model.UserNameOrEmailAddress,
              model.Password,
              await GetTenancyNameFromTenantId(model.TenantId)
            );
            var returnUrl = model.ReturnUrl;

            if (model.SingleSignIn.HasValue && model.SingleSignIn.Value && loginResult.Result == AbpLoginResultType.Success)
            {
                loginResult.User.SetSignInToken();
                returnUrl = AddSingleSignInParametersToReturnUrl(model.ReturnUrl, loginResult.User.SignInToken, loginResult.User.Id, loginResult.User.TenantId);
            }

            //Password reset
            if (loginResult.User.ShouldChangePasswordOnNextLogin)
            {
                loginResult.User.SetNewPasswordResetCode();
                return new AppAuthenticateResultModel
                {
                    ShouldResetPassword = true,
                    PasswordResetCode = loginResult.User.PasswordResetCode,
                    UserId = loginResult.User.Id,
                    ReturnUrl = returnUrl
                };
            }

            //Two factor auth
            AuthenticateModel zeroAuthenticateModel = new AuthenticateModel
            {
                UserNameOrEmailAddress = model.UserNameOrEmailAddress,
                Password = model.Password,
                TwoFactorVerificationCode = model.TwoFactorVerificationCode,
                RememberClient = model.RememberClient,
                TwoFactorRememberClientToken = model.TwoFactorRememberClientToken,
                SingleSignIn = model.SingleSignIn,
                ReturnUrl = model.ReturnUrl
            };

            await _userManager.InitializeOptionsAsync(loginResult.Tenant?.Id);
            string twoFactorRememberClientToken = null;
            if (await IsTwoFactorAuthRequiredAsync(loginResult, zeroAuthenticateModel))
            {
                if (model.TwoFactorVerificationCode.IsNullOrEmpty())
                {
                    //Add a cache item which will be checked in SendTwoFactorAuthCode to prevent sending unwanted two factor code to users.
                    _cacheManager
                        .GetTwoFactorCodeCache()
                        .Set(
                            loginResult.User.ToUserIdentifier().ToString(),
                            new TwoFactorCodeCacheItem()
                        );

                    return new AppAuthenticateResultModel
                    {
                        RequiresTwoFactorVerification = true,
                        UserId = loginResult.User.Id,
                        TwoFactorAuthProviders = await _userManager.GetValidTwoFactorProvidersAsync(loginResult.User),
                        ReturnUrl = returnUrl
                    };
                }

                twoFactorRememberClientToken = await TwoFactorAuthenticateAsync(loginResult.User, zeroAuthenticateModel);
            }

            //Login!
            var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));

            // Nuage stuff - LoginAudit
            var audit = new NcDeviceAndUserLoginAudit
            {
                DeviceId = model.DeviceId,
                UserId = loginResult.User.Id,
                LoginDatetime = Clock.Now
            };

            //insert LoginAudit - TODO - should be Mongodb
            await _deviceAndUserLoginAuditRepository.InsertAsync(audit);

            return new AppAuthenticateResultModel
            {
                AccessToken = accessToken,
                EncryptedAccessToken = GetEncrpytedAccessToken(accessToken),
                ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
                TwoFactorRememberClientToken = twoFactorRememberClientToken,
                UserId = loginResult.User.Id,
                ReturnUrl = returnUrl,
                UserDisplayName = loginResult.User.FullName,
                TenantName = await GetTenancyNameFromTenantId(model.TenantId),
                NuageSettingsDto = await _tenantSettingsAppService.GetNuageSettingsAsync()
            };
        }

        [HttpPost]
        public async Task SendTwoFactorAuthCode([FromBody] SendTwoFactorAuthCodeModel model)
        {
            var cacheKey = new UserIdentifier(AbpSession.TenantId, model.UserId).ToString();

            var cacheItem = await _cacheManager
                .GetTwoFactorCodeCache()
                .GetOrDefaultAsync(cacheKey);

            if (cacheItem == null)
            {
                //There should be a cache item added in Authenticate method! This check is needed to prevent sending unwanted two factor code to users.
                throw new UserFriendlyException(L("SendSecurityCodeErrorMessage"));
            }

            var user = await _userManager.FindByIdAsync(model.UserId.ToString());

            if (model.Provider != GoogleAuthenticatorProvider.Name)
            {
                cacheItem.Code = await _userManager.GenerateTwoFactorTokenAsync(user, model.Provider);
                var message = L("EmailSecurityCodeBody", cacheItem.Code);

                if (model.Provider == "Email")
                {
                    await _emailSender.SendAsync(await _userManager.GetEmailAsync(user), L("EmailSecurityCodeSubject"),
                        message);
                }
                else if (model.Provider == "Phone")
                {
                    await _smsSender.SendAsync(await _userManager.GetPhoneNumberAsync(user), message);
                }
            }

            _cacheManager.GetTwoFactorCodeCache().Set(
                    cacheKey,
                    cacheItem
                );
            _cacheManager.GetCache("ProviderCache").Set(
                "Provider",
                model.Provider
            );
        }

        [HttpPost]
        public async Task<ImpersonatedAuthenticateResultModel> ImpersonatedAuthenticate(string impersonationToken)
        {
            var result = await _impersonationManager.GetImpersonatedUserAndIdentity(impersonationToken);
            var accessToken = CreateAccessToken(CreateJwtClaims(result.Identity));

            return new ImpersonatedAuthenticateResultModel
            {
                AccessToken = accessToken,
                EncryptedAccessToken = GetEncrpytedAccessToken(accessToken),
                ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds
            };
        }

        [HttpPost]
        public async Task<SwitchedAccountAuthenticateResultModel> LinkedAccountAuthenticate(string switchAccountToken)
        {
            var result = await _userLinkManager.GetSwitchedUserAndIdentity(switchAccountToken);
            var accessToken = CreateAccessToken(CreateJwtClaims(result.Identity));

            return new SwitchedAccountAuthenticateResultModel
            {
                AccessToken = accessToken,
                EncryptedAccessToken = GetEncrpytedAccessToken(accessToken),
                ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds
            };
        }

        [HttpGet]
        public List<ExternalLoginProviderInfoModel> GetExternalAuthenticationProviders()
        {
            return ObjectMapper.Map<List<ExternalLoginProviderInfoModel>>(_externalAuthConfiguration.Providers);
        }

        private async Task<bool> IsTwoFactorAuthRequiredAsync(AbpLoginResult<Tenant, User> loginResult, AuthenticateModel authenticateModel)
        {
            if (!await SettingManager.GetSettingValueAsync<bool>(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsEnabled))
            {
                return false;
            }

            if (!loginResult.User.IsTwoFactorEnabled)
            {
                return false;
            }

            if ((await _userManager.GetValidTwoFactorProvidersAsync(loginResult.User)).Count <= 0)
            {
                return false;
            }

            if (await TwoFactorClientRememberedAsync(loginResult.User.ToUserIdentifier(), authenticateModel))
            {
                return false;
            }

            return true;
        }

        private async Task<bool> TwoFactorClientRememberedAsync(UserIdentifier userIdentifier, AuthenticateModel authenticateModel)
        {
            if (!await SettingManager.GetSettingValueAsync<bool>(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsRememberBrowserEnabled))
            {
                return false;
            }

            if (string.IsNullOrWhiteSpace(authenticateModel.TwoFactorRememberClientToken))
            {
                return false;
            }

            try
            {
                var validationParameters = new TokenValidationParameters
                {
                    ValidAudience = _configuration.Audience,
                    ValidIssuer = _configuration.Issuer,
                    IssuerSigningKey = _configuration.SecurityKey
                };

                foreach (var validator in _jwtOptions.Value.SecurityTokenValidators)
                {
                    if (validator.CanReadToken(authenticateModel.TwoFactorRememberClientToken))
                    {
                        try
                        {
                            SecurityToken validatedToken;
                            var principal = validator.ValidateToken(authenticateModel.TwoFactorRememberClientToken, validationParameters, out validatedToken);
                            var useridentifierClaim = principal.FindFirst(c => c.Type == UserIdentifierClaimType);
                            if (useridentifierClaim == null)
                            {
                                return false;
                            }

                            return useridentifierClaim.Value == userIdentifier.ToString();
                        }
                        catch (Exception ex)
                        {
                            Logger.Debug(ex.ToString(), ex);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Debug(ex.ToString(), ex);
            }

            return false;
        }

        /* Checkes two factor code and returns a token to remember the client (browser) if needed */
        private async Task<string> TwoFactorAuthenticateAsync(User user, AuthenticateModel authenticateModel)
        {
            var twoFactorCodeCache = _cacheManager.GetTwoFactorCodeCache();
            var userIdentifier = user.ToUserIdentifier().ToString();
            var cachedCode = await twoFactorCodeCache.GetOrDefaultAsync(userIdentifier);
            var provider = _cacheManager.GetCache("ProviderCache").Get("Provider", cache => cache).ToString();

            if (provider == GoogleAuthenticatorProvider.Name)
            {
                if (!await _googleAuthenticatorProvider.ValidateAsync("TwoFactor", authenticateModel.TwoFactorVerificationCode, _userManager, user))
                {
                    throw new UserFriendlyException(L("InvalidSecurityCode"));
                }
            }
            else if (cachedCode?.Code == null || cachedCode.Code != authenticateModel.TwoFactorVerificationCode)
            {
                throw new UserFriendlyException(L("InvalidSecurityCode"));
            }

            //Delete from the cache since it was a single usage code
            await twoFactorCodeCache.RemoveAsync(userIdentifier);

            if (authenticateModel.RememberClient)
            {
                if (await SettingManager.GetSettingValueAsync<bool>(AbpZeroSettingNames.UserManagement.TwoFactorLogin.IsRememberBrowserEnabled))
                {
                    return CreateAccessToken(new[]
                        {
                            new Claim(UserIdentifierClaimType, user.ToUserIdentifier().ToString())
                        },
                        TimeSpan.FromDays(365)
                    );
                }
            }

            return null;
        }

        private async Task<string> GetTenancyNameFromTenantId(int tenantId)
        {
            var tenant = await _tenantManager.FindByIdAsync(tenantId);
            if (tenant == null)
            {
                return null;
            }
            return tenant.TenancyName;
        }

        private async Task<AbpLoginResult<Tenant, User>> GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName)
        {
            var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);

            switch (loginResult.Result)
            {
                case AbpLoginResultType.Success:
                    return loginResult;
                default:
                    throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName);
            }
        }

        private string CreateAccessToken(IEnumerable<Claim> claims, TimeSpan? expiration = null)
        {
            var now = DateTime.UtcNow;

            var jwtSecurityToken = new JwtSecurityToken(
                issuer: _configuration.Issuer,
                audience: _configuration.Audience,
                claims: claims,
                notBefore: now,
                expires: now.Add(expiration ?? _configuration.Expiration),
                signingCredentials: _configuration.SigningCredentials
            );

            return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
        }

        private string GetEncrpytedAccessToken(string accessToken)
        {
            return SimpleStringCipher.Instance.Encrypt(accessToken, AppConsts.DefaultPassPhrase);
        }

        private List<Claim> CreateJwtClaims(ClaimsIdentity identity)
        {
            var claims = identity.Claims.ToList();
            var nameIdClaim = claims.First(c => c.Type == _identityOptions.ClaimsIdentity.UserIdClaimType);

            if (_identityOptions.ClaimsIdentity.UserIdClaimType != JwtRegisteredClaimNames.Sub)
            {
                claims.Add(new Claim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value));
            }

            claims.AddRange(new[]
            {
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
            });

            return claims;
        }

        private string AddSingleSignInParametersToReturnUrl(string returnUrl, string signInToken, long userId, int? tenantId)
        {
            returnUrl += (returnUrl.Contains("?") ? "&" : "?") +
                         "accessToken=" + signInToken +
                         "&userId=" + Convert.ToBase64String(Encoding.UTF8.GetBytes(userId.ToString()));
            if (tenantId.HasValue)
            {
                returnUrl += "&tenantId=" + Convert.ToBase64String(Encoding.UTF8.GetBytes(tenantId.Value.ToString()));
            }

            return returnUrl;
        }

        protected virtual async Task<User> CreateDataProviderUser(AppAuthenticateModel model, DataProviderUserDto DataProviderUser)
        {
            User user = new User();
                using (var unitOfWork = _unitOfWorkManager.Begin())
                {
                    user.TenantId = model.TenantId;
                    user.UserName = model.UserNameOrEmailAddress;
                    user.Password = model.Password;
                    user.IsEmailConfirmed = false; //isEmailConfirmed,
                    user.IsActive = true;
                    user.Name = DataProviderUser.DisplayName;
                    user.Name = DataProviderUser.Firstname;
                    user.Surname = DataProviderUser.Lastname;
                    user.Roles = new List<UserRole>();

                    //we must have an email unique to each tenant
                    if (!String.IsNullOrEmpty(DataProviderUser.Email))
                    {
                        user.EmailAddress = DataProviderUser.Email;
                    }
                    else
                    {
                        user.EmailAddress = user.UserName.ToLowerInvariant() + "@noemail.io";
                    }
                    user.SetNormalizedNames();

                    user.Password = _passwordHasher.HashPassword(user, model.Password);

                    foreach (var defaultRole in await _roleManager.Roles.Where(r => r.IsDefault).ToListAsync())
                    {
                        user.Roles.Add(new UserRole(model.TenantId, user.Id, defaultRole.Id));
                    }
                    CheckErrors(await _userManager.CreateAsync(user));

                    AbpSession.Use(model.TenantId, user.Id);
                    await SettingManager.ChangeSettingForUserAsync(new UserIdentifier(model.TenantId, user.Id), NuageSettings.NuageUserManagement.DataProviderUserId, DataProviderUser.UserId);
                    await unitOfWork.CompleteAsync();
                }
            return user;
        }
    }
}

Cheers, Bob

Answer

Hi @ismcagdas , thanks for getting back. My AppTokenAuthController inherits from [YourProjectName]ControllerBase and it used only for logging in through an Ionic app :

namespace Nuagecare.Web.Controllers
{
    [Route("api/[controller]/[action]")]
    public class AppTokenAuthController : NuagecareControllerBase
    {

But I think we are missing the point in concentrating on my code. The fact is that when I login to the system using the standard angular front-end the Zero standard TokenAuthController is used. No audit-log entry is made from the TokenAuthController.Authenticate method. I would like to see logins in my audit log so I can implement an audit log table per user. It would be nice to see an initial login entry followed by the standard audit-log entries.

Is it possible to have logins audited and, if so, how?

Answer

@Aaron, In my implementation I have replaced both IAuditingStore and IEntityHistoryStore:

Configuration.ReplaceService<IEntityHistoryStore, MongodbEntityHistoryStore>(DependencyLifeStyle.Transient);
Configuration.ReplaceService<IAuditingStore, MongodbAuditingStore>(DependencyLifeStyle.Transient);

and added a new audit log app service, MongodbAuditLogAppService.cs. The three tables in Azure's SQL Server (dbo.AbpEntityPropertyChanges, dbo.AbpAuditLogs and dbo.AbpEntityChanges) meant I would have to upgrade my SQL Server just to store this immutable data. Because my system is built on data protection compliancy this history is a key part of the project. I have made the decision to store immutable history data in Mongodb. Here is my MongodbAuditingStore:

using Abp.Auditing;
using Abp.Dependency;
using Abp.Domain.Repositories;
using Abp.Runtime.Session;
using AutoMapper;
using MongoDB.Bson;
using MongoDB.Driver;
using Nuagecare.App.Auditing;
using Nuagecare.Mongodb;
using Nuagecare.MultiTenancy;
using System;
using System.Threading.Tasks;

namespace Nuagecare.App.Mongodb
{
    public class MongodbAuditingStore : IAuditingStore, ITransientDependency
    {
        private readonly IRepository<Tenant> _tenantRepository;
        public IAbpSession AbpSession { get; set; }

        /// <summary>
        /// Creates  a new <see cref="AuditingStore"/>.
        /// </summary>
        public MongodbAuditingStore(
            IRepository<Tenant> tenantRepository)
        {
            AbpSession = NullAbpSession.Instance;
            _tenantRepository = tenantRepository;
        }

        public async Task SaveAsync(AuditInfo auditInfo)
        {
            var mongoAuditInfo = Mapper.Map<MongoAuditInfo>(auditInfo);
            var dataBaseName = "projectname";

            var mongoClient = new MongodbConfig().Initialize();
            if (AbpSession.TenantId != null)
            {
                dataBaseName = GetTenancyNameFromTenantId(Convert.ToInt32(AbpSession.TenantId));
            }
            IMongoDatabase db = mongoClient.GetDatabase(dataBaseName);
            var collection = db.GetCollection<BsonDocument>("auditLog");
            var document = mongoAuditInfo.ToBsonDocument();
            await collection.InsertOneAsync(document);
        }

        private string GetTenancyNameFromTenantId(int tenantId)
        {
            var tenant = _tenantRepository.FirstOrDefault(tenantId);
            if (tenant == null)
            {
                return "projectname";
            }
            return tenant.TenancyName;
        }
    }
}

Everything in the modified system is working perfectly, I am storing entity history and audit log info in Mongodb and MongodbAuditLogAppService.cs returns the correct data to the angular projects.

On the subject of my own AppAuthenticate method in my own controller, AppTokenAuthController this is implemented to support tokens for Ionic apps and requires some bespoke code. Here is my AppAuthenticate method:

[HttpPost]
[DontWrapResult(WrapOnError = true)]
[Audited]
public virtual async Task<AppAuthenticateResultModel> AppAuthenticate([FromBody] AppAuthenticateModel model)
{
    // Nuage stuff - null tenant value
    if (model.TenantId == 0)
    {
        throw new UserFriendlyException(L("PleaseRegisterThisDeviceBeforeSigningIn."));
    }

    //if the user exists in the data provider but not in Zero we need to add a new user....
    using (_unitOfWorkManager.Current.SetTenantId(model.TenantId))
    {
        //see https://forum.aspnetboilerplate.com/viewtopic.php?f=5&t=10044&p=23035&hilit=set+abpsession&sid=99db30a39413f544f1b2bfeb49ff4d18#p23035
        AbpSession.Use(model.TenantId, null);
        var tenantDataProvider = await SettingManager.GetSettingValueAsync(NuageSettings.NuageTenantManagement.DataProvider);

        var DataProviderApiUrl = await SettingManager.GetSettingValueAsync(NuageSettings.NuageTenantManagement.DataProviderApiUrl);
        if (String.IsNullOrEmpty(DataProviderApiUrl))
        {
            throw new UserFriendlyException(L("InvalidDataProviderApiUrl"));
        }

        if (tenantDataProvider != "Nuage")
        {
            var zeroUser = await _userManager.FindByNameOrEmailAsync(model.UserNameOrEmailAddress);
            if (zeroUser == null)
            {
                await _userPolicy.CheckMaxUserCountAsync(model.TenantId);
                var DataProviderUser = await _dataProviderAPIProvider.DataProviderLoginAndReturnUser(model.UserNameOrEmailAddress, model.Password);
                if (DataProviderUser != null)
                {
                    await CreateDataProviderUser(model, DataProviderUser);
                }
                else
                {
                    throw new UserFriendlyException(L("InvalidUserNameOrPassword"));
                }
            }
        }
    }

    var loginResult = await GetLoginResultAsync(
        model.UserNameOrEmailAddress,
        model.Password,
        await GetTenancyNameFromTenantId(model.TenantId)
    );
    var returnUrl = model.ReturnUrl;

    if (model.SingleSignIn.HasValue && model.SingleSignIn.Value && loginResult.Result == AbpLoginResultType.Success)
    {
        loginResult.User.SetSignInToken();
        returnUrl = AddSingleSignInParametersToReturnUrl(model.ReturnUrl, loginResult.User.SignInToken, loginResult.User.Id, loginResult.User.TenantId);
    }

    //Password reset
    if (loginResult.User.ShouldChangePasswordOnNextLogin)
    {
        loginResult.User.SetNewPasswordResetCode();
        return new AppAuthenticateResultModel
        {
            ShouldResetPassword = true,
            PasswordResetCode = loginResult.User.PasswordResetCode,
            UserId = loginResult.User.Id,
            ReturnUrl = returnUrl
        };
    }

    //Two factor auth
    AuthenticateModel zeroAuthenticateModel = new AuthenticateModel
    {
        UserNameOrEmailAddress = model.UserNameOrEmailAddress,
        Password = model.Password,
        TwoFactorVerificationCode = model.TwoFactorVerificationCode,
        RememberClient = model.RememberClient,
        TwoFactorRememberClientToken = model.TwoFactorRememberClientToken,
        SingleSignIn = model.SingleSignIn,
        ReturnUrl = model.ReturnUrl
    };

    await _userManager.InitializeOptionsAsync(loginResult.Tenant?.Id);
    string twoFactorRememberClientToken = null;
    if (await IsTwoFactorAuthRequiredAsync(loginResult, zeroAuthenticateModel))
    {
        if (model.TwoFactorVerificationCode.IsNullOrEmpty())
        {
            //Add a cache item which will be checked in SendTwoFactorAuthCode to prevent sending unwanted two factor code to users.
            _cacheManager
                .GetTwoFactorCodeCache()
                .Set(
                    loginResult.User.ToUserIdentifier().ToString(),
                    new TwoFactorCodeCacheItem()
                );

            return new AppAuthenticateResultModel
            {
                RequiresTwoFactorVerification = true,
                UserId = loginResult.User.Id,
                TwoFactorAuthProviders = await _userManager.GetValidTwoFactorProvidersAsync(loginResult.User),
                ReturnUrl = returnUrl
            };
        }

        twoFactorRememberClientToken = await TwoFactorAuthenticateAsync(loginResult.User, zeroAuthenticateModel);
    }

    //Login!
    var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));

    // Nuage stuff - LoginAudit
    var audit = new NcDeviceAndUserLoginAudit
    {
        DeviceId = model.DeviceId,
        UserId = loginResult.User.Id,
        LoginDatetime = Clock.Now
    };

    //insert LoginAudit - TODO - should be Mongodb
    await _deviceAndUserLoginAuditRepository.InsertAsync(audit);

    return new AppAuthenticateResultModel
    {
        AccessToken = accessToken,
        EncryptedAccessToken = GetEncrpytedAccessToken(accessToken),
        ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
        TwoFactorRememberClientToken = twoFactorRememberClientToken,
        UserId = loginResult.User.Id,
        ReturnUrl = returnUrl,
        UserDisplayName = loginResult.User.FullName,
        TenantName = await GetTenancyNameFromTenantId(model.TenantId),
        NuageSettingsDto = await _tenantSettingsAppService.GetNuageSettingsAsync()
    };
}

I apologise in advance for my code, I'm not up there with the best of them. However, I think we're getting too deep into details. The fact is that the AppAuthenticate method in my own controller, AppTokenAuthController does not place an entry into the audit logs. But, if I login using the angular front end the standard Authenticate method in the Zero controller, TokenAuthController it also does not place an entry into the audit logs. So, the question remains,

How do store logins in the audit logs?

Answer

@ryancyq that's almost 600 lines of code, are you sure you want me to post it? There are some modifications made to allow logins from an Ionic app otherwise it's fairly standard. If you want me to share I will gladly do so.

Answer

Hi Aaron, thanks for getting back. I have set the configuration for Auditing:

Configuration.Auditing.IsEnabledForAnonymousUsers = true;

. There is nothing in the AbpAuditLogs table and AuditingStore.SaveAsync (...) is not fired when debugging logins.

"MongodbAuditLogAppService" is a type which is not valid in the current context. I also tried:

Configuration.ReplaceService(typeof(IAuditLogAppService), () =>
{
    IocManager.IocContainer.Register(
        Component.For<IAuditLogAppService>()
            .ImplementedBy<MongodbAuditLogAppService>()
            .LifestyleTransient()
        );
});

I have used a few IoC containers, I confess to not understanding the hieroglyphics of lifetimes but....

Why is so hard to simply replace a Zero service with my own service?

Which module should this code go in? I try place in [Projectname]ApplicationModule in the Application project and get "The non-generic method 'IAppStartupConfiguration.ReplaceService(Type, Action) cannot be used with type arguments". I place in [Projectname]WebCoreModule in the Web.Core project and nothing happens, AuditLogService is still called. It can not be placed in the [Projectname]CoreModule in the Core project because of dependencies.

Thanks, Aaron, it wasn't that simple. I finally managed it by deleting some files from users/appdata, uninstalling and re-installing node and yarn and going round in circles for a couple of hours or so. Nothing to do with Zero, but thanks for your input anyway.

@ismcagdas, thanks for getting back. One of the things I like about working with your solution is your diligent attention to all github and forum posts. I don't want to suggest a breaking change when you have other priorities. I can work without this feature but it took me a while to figure out what was happening. I use the settings feature a lot for tenant parameters and the absence of settings in the database just makes it a little bit difficult to work out what's going on when working with third party data providers. Thanks again, keep up the good work!

Showing 371 to 380 of 477 entries