Exemplo n.º 1
0
        protected override DefaultMetadataDetails[] CreatePropertyDetails(ModelMetadataIdentity key)
        {
            // Call the base implementation
            var propsDetails = base.CreatePropertyDetails(key);

            // Customize the label of Resource properties
            if (key.ModelType.IsSameOrSubClassOf <ResourceForSave>())
            {
                // Get the route data from http context
                // Loop over the properties and special treatment to the dynamic ones
                foreach (var propDetails in propsDetails)
                {
                    var defaultName = propDetails.ModelAttributes.PropertyAttributes
                                      .OfType <DisplayAttribute>().FirstOrDefault()?.Name ?? propDetails.Key.Name;

                    DisplayMetadata displayMetadata;

                    switch (propDetails.Key.Name)
                    {
                    case nameof(Resource.Identifier):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.IdentifierVisibility, e => e.IdentifierLabel, e => e.IdentifierLabel2, e => e.IdentifierLabel3, defaultName);
                        break;

                    // All dynamically labelled properties
                    case nameof(Resource.Currency):
                    case nameof(Resource.CurrencyId):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.CurrencyVisibility, e => e.CurrencyLabel, e => e.CurrencyLabel2, e => e.CurrencyLabel3, defaultName);
                        break;

                    case nameof(Resource.MonetaryValue):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.MonetaryValueVisibility, e => e.MonetaryValueLabel, e => e.MonetaryValueLabel2, e => e.MonetaryValueLabel3, defaultName);
                        break;

                    case nameof(Resource.CountUnit):
                    case nameof(Resource.CountUnitId):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.CountUnitVisibility, e => e.CountUnitLabel, e => e.CountUnitLabel2, e => e.CountUnitLabel3, defaultName);
                        break;

                    case nameof(Resource.Count):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.CountVisibility, e => e.CountLabel, e => e.CountLabel2, e => e.CountLabel3, defaultName);
                        break;

                    case nameof(Resource.MassUnit):
                    case nameof(Resource.MassUnitId):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.MassUnitVisibility, e => e.MassUnitLabel, e => e.MassUnitLabel2, e => e.MassUnitLabel3, defaultName);
                        break;

                    case nameof(Resource.Mass):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.MassVisibility, e => e.MassLabel, e => e.MassLabel2, e => e.MassLabel3, defaultName);
                        break;

                    case nameof(Resource.VolumeUnit):
                    case nameof(Resource.VolumeUnitId):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.VolumeUnitVisibility, e => e.VolumeUnitLabel, e => e.VolumeUnitLabel2, e => e.VolumeUnitLabel3, defaultName);
                        break;

                    case nameof(Resource.Volume):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.VolumeVisibility, e => e.VolumeLabel, e => e.VolumeLabel2, e => e.VolumeLabel3, defaultName);
                        break;

                    case nameof(Resource.TimeUnit):
                    case nameof(Resource.TimeUnitId):
                        displayMetadata = LocalizeResourceProperty
                                              (e => e.TimeUnitVisibility, e => e.TimeUnitLabel, e => e.TimeUnitLabel2, e => e.TimeUnitLabel3, defaultName);
                        break;

                    case nameof(Resource.Time):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.TimeVisibility, e => e.TimeLabel, e => e.TimeLabel2, e => e.TimeLabel3, defaultName);
                        break;

                    case nameof(Resource.AvailableSince):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.AvailableSinceVisibility, e => e.AvailableSinceLabel, e => e.AvailableSinceLabel2, e => e.AvailableSinceLabel3, defaultName);
                        break;

                    case nameof(Resource.AvailableTill):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.AvailableTillVisibility, e => e.AvailableTillLabel, e => e.AvailableTillLabel2, e => e.AvailableTillLabel3, defaultName);
                        break;

                    case nameof(Resource.Lookup1):
                    case nameof(Resource.Lookup1Id):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.Lookup1Visibility, e => e.Lookup1Label, e => e.Lookup1Label2, e => e.Lookup1Label3, defaultName);
                        break;

                    case nameof(Resource.Lookup2):
                    case nameof(Resource.Lookup2Id):
                        displayMetadata = LocalizeResourceProperty(
                            e => e.Lookup2Visibility, e => e.Lookup2Label, e => e.Lookup2Label2, e => e.Lookup2Label3, defaultName);
                        break;

                    //case nameof(Resource.Lookup3):
                    //case nameof(Resource.Lookup3Id):
                    //    displayMetadata = LocalizeResourceProperty(e => e.Lookup3Visibility, e => e.Lookup3Label, e => e.Lookup3Label2, e => e.Lookup3Label3, defaultName);
                    //    break;

                    //case nameof(Resource.Lookup4):
                    //case nameof(Resource.Lookup4Id):
                    //    displayMetadata = LocalizeResourceProperty(e => e.Lookup4Visibility, e => e.Lookup4Label, e => e.Lookup4Label2, e => e.Lookup4Label3, defaultName);
                    //    break;

                    //case nameof(Resource.Lookup5):
                    //case nameof(Resource.Lookup5Id):
                    //    displayMetadata = LocalizeResourceProperty(e => e.Lookup5Visibility, e => e.Lookup5Label, e => e.Lookup5Label2, e => e.Lookup5Label3, defaultName);
                    //    break;

                    default:
                        displayMetadata = null;
                        break;
                    }

                    propDetails.DisplayMetadata = displayMetadata;
                }
            }

            // Customize the label of Agent properties
            if (key.ModelType.IsSameOrSubClassOf <AgentForSave>())
            {
                // Get the route data from http context
                // Loop over the properties and special treatment to the dynamic ones
                foreach (var propDetails in propsDetails)
                {
                    var defaultName = propDetails.ModelAttributes.PropertyAttributes
                                      .OfType <DisplayAttribute>().FirstOrDefault()?.Name ?? propDetails.Key.Name;
                    var displayMetadata = propDetails.Key.Name switch
                    {
                        // All dynamically labelled properties
                        nameof(Agent.TaxIdentificationNumber) => LocalizeAgentSpecificProperty(e => e.TaxIdentificationNumberVisibility, defaultName),
                        nameof(Agent.StartDate) => LocalizeAgentProperty(e => e.StartDateVisibility, e => e.StartDateLabel, e => e.StartDateLabel2, e => e.StartDateLabel3, defaultName),
                        nameof(Agent.JobId) => LocalizeAgentSpecificProperty(e => e.JobVisibility, defaultName),//  case nameof(Agent.Job): TODO
                        nameof(Agent.BasicSalary) => LocalizeAgentSpecificProperty(e => e.BasicSalaryVisibility, defaultName),
                        nameof(Agent.TransportationAllowance) => LocalizeAgentSpecificProperty(e => e.TransportationAllowanceVisibility, defaultName),
                        nameof(Agent.OvertimeRate) => LocalizeAgentSpecificProperty(e => e.OvertimeRateVisibility, defaultName),
                        nameof(Agent.BankAccountNumber) => LocalizeAgentSpecificProperty(e => e.BankAccountNumberVisibility, defaultName),
                        _ => null,
                    };
                    propDetails.DisplayMetadata = displayMetadata;
                }
            }

            // In general: append the language name to the labels of multilingual
            foreach (var propDetails in propsDetails)
            {
                var att = propDetails.ModelAttributes.PropertyAttributes
                          .OfType <MultilingualDisplayAttribute>().FirstOrDefault();

                if (att != null)
                {
                    var name = att.Name ?? "";
                    var lang = att.Language;

                    propDetails.DisplayMetadata = new DisplayMetadata
                    {
                        DisplayName = () =>
                        {
                            var info = _tenantInfoAccessor.GetCurrentInfo();
                            if (info == null)
                            {
                                // Developer mistake
                                throw new InvalidOperationException("TenantInfo is not set");
                            }

                            var result = lang switch
                            {
                                Language.Primary => _localizer[name] + PrimaryPostfix(info),
                                Language.Secondary => string.IsNullOrWhiteSpace(info.SecondaryLanguageId) ? Constants.HIDDEN_FIELD : _localizer[name] + SecondaryPostfix(info),
                                Language.Ternary => string.IsNullOrWhiteSpace(info.TernaryLanguageId) ? Constants.HIDDEN_FIELD : _localizer[name] + TernaryPostfix(info),
                                _ => _localizer[name],
                            };
                            ;

                            return(result);
                        }
                    };
                }
            }

            return(propsDetails);
        }

        DisplayMetadata LocalizeResourceProperty(
            Func <ResourceDefinitionForClient, string> visibilityFunc,
            Func <ResourceDefinitionForClient, string> s1Func,
            Func <ResourceDefinitionForClient, string> s2Func,
            Func <ResourceDefinitionForClient, string> s3Func,
            string defaultDisplayName)
        {
            return(LocalizeProperty(
                       (tenantId, definitionId) => _definitionsCache.GetDefinitionsIfCached(tenantId)?.Data?.Resources?.GetValueOrDefault(definitionId),
                       visibilityFunc, s1Func, s2Func, s3Func, defaultDisplayName));
        }

        DisplayMetadata LocalizeAgentProperty(
            Func <AgentDefinitionForClient, string> visibilityFunc,
            Func <AgentDefinitionForClient, string> s1Func,
            Func <AgentDefinitionForClient, string> s2Func,
            Func <AgentDefinitionForClient, string> s3Func,
            string defaultDisplayName)
        {
            return(LocalizeProperty(
                       (tenantId, definitionId) => _definitionsCache.GetDefinitionsIfCached(tenantId)?.Data?.Agents?.GetValueOrDefault(definitionId),
                       visibilityFunc, s1Func, s2Func, s3Func, defaultDisplayName));
        }

        DisplayMetadata LocalizeAgentSpecificProperty(
            Func <AgentDefinitionForClient, string> visibilityFunc,
            string defaultDisplayName)
        {
            return(LocalizeProperty(
                       (tenantId, definitionId) => _definitionsCache.GetDefinitionsIfCached(tenantId)?.Data?.Agents?.GetValueOrDefault(definitionId),
                       visibilityFunc, e => null, e => null, e => null, defaultDisplayName));
        }

        DisplayMetadata LocalizeProperty <TDefinitionForClient>(
            Func <int, string, TDefinitionForClient> definitionFunc,
            Func <TDefinitionForClient, string> visibilityFunc,
            Func <TDefinitionForClient, string> s1Func,
            Func <TDefinitionForClient, string> s2Func,
            Func <TDefinitionForClient, string> s3Func,
            string defaultDisplayName)
        {
            return(new DisplayMetadata
            {
                // Return a dynamic display name from the definitions, and fall back to
                // the default if non are available. Be as forgiving as possible
                DisplayName = () =>
                {
                    string result = _localizer[defaultDisplayName];
                    var routeData = _httpContextAccessor.HttpContext.GetRouteData();
                    var definitionId = routeData.Values["definitionId"]?.ToString();

                    if (!string.IsNullOrWhiteSpace(definitionId))
                    {
                        var tenantId = _tenantIdAccessor.GetTenantId();
                        var definition = definitionFunc(tenantId, definitionId);

                        if (definition != null)
                        {
                            if (visibilityFunc(definition) == null)
                            {
                                result = Constants.HIDDEN_FIELD;
                            }
                            else
                            {
                                result = _tenantInfoAccessor.GetCurrentInfo().Localize(
                                    s1Func(definition),
                                    s2Func(definition),
                                    s3Func(definition)) ?? result;
                            }
                        }
                    }

                    return result;
                }
            });
        }
Exemplo n.º 2
0
            public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
            {
                // (1) Make sure the API caller have provided a tenantId, and extract it
                try
                {
                    var cancellation = context.HttpContext.RequestAborted;
                    int tenantId     = _tenantIdAccessor.GetTenantId();

                    // Init the database connection...
                    // The client sometimes makes ambient API calls, not in response to user interaction
                    // Such calls should not update LastAccess of that user
                    bool silent = context.HttpContext.Request.Query["silent"].FirstOrDefault()?.ToString()?.ToLower() == "true";
                    await _appRepo.InitConnectionAsync(tenantId, setLastActive : !silent, cancellation);

                    // (2) Make sure the user is a member of this tenant
                    UserInfo userInfo = await _appRepo.GetUserInfoAsync(cancellation);

                    if (userInfo.UserId == null)
                    {
                        // If there is no user cut the pipeline short and return a Forbidden 403
                        context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);

                        // This indicates to the client to discard all cached information about this
                        // company since the user is no longer a member of it
                        context.HttpContext.Response.Headers.Add("x-settings-version", Constants.Unauthorized);
                        context.HttpContext.Response.Headers.Add("x-definitions-version", Constants.Unauthorized);
                        context.HttpContext.Response.Headers.Add("x-permissions-version", Constants.Unauthorized);
                        context.HttpContext.Response.Headers.Add("x-user-settings-version", Constants.Unauthorized);

                        return;
                    }

                    var userId        = userInfo.UserId.Value;
                    var externalId    = _externalUserAccessor.GetUserId();
                    var externalEmail = _externalUserAccessor.GetUserEmail();

                    // (3) If the user exists but new, set the External Id
                    if (userInfo.ExternalId == null)
                    {
                        // Update external Id in this tenant database
                        await _appRepo.Users__SetExternalIdByUserId(userId, externalId);

                        // Update external Id in the central Admin database too (To avoid an awkward situation
                        // where a user exists on the tenant but not on the Admin db, if they change their email in between)
                        var adminRepo = _serviceProvider.GetRequiredService <AdminRepository>();
                        await adminRepo.DirectoryUsers__SetExternalIdByEmail(externalEmail, externalId);
                    }

                    else if (userInfo.ExternalId != externalId)
                    {
                        // Note: there is the edge case of identity providers who allow email recycling. I.e. we can get the same email twice with
                        // two different external Ids. This issue is so unlikely to naturally occur and cause problems here that we are not going
                        // to handle it for now. It can however happen artificually if the application is re-configured to a new identity provider,
                        // or if someone messed with the identity database directly, but again out of scope for now.
                        context.Result = new BadRequestObjectResult("The sign-in email already exists but with a different external Id");
                        return;
                    }

                    // (4) If the user's email address has changed at the identity server, update it locally
                    else if (userInfo.Email != externalEmail)
                    {
                        await _appRepo.Users__SetEmailByUserId(userId, externalEmail);
                    }

                    // (5) Set the tenant info in the context, to make it accessible for model metadata providers
                    var tenantInfo = await _appRepo.GetTenantInfoAsync(cancellation);

                    _tenantInfoAccessor.SetInfo(tenantId, tenantInfo);

                    // (6) Ensure the freshness of the definitions and settings caches
                    {
                        var databaseVersion = tenantInfo.DefinitionsVersion;
                        var serverVersion   = _definitionsCache.GetDefinitionsIfCached(tenantId)?.Version;

                        if (serverVersion == null || serverVersion != databaseVersion)
                        {
                            // Update the cache
                            var definitions = await DefinitionsService.LoadDefinitionsForClient(_appRepo, cancellation);

                            if (!cancellation.IsCancellationRequested)
                            {
                                _definitionsCache.SetDefinitions(tenantId, definitions);
                            }
                        }
                    }
                    {
                        var databaseVersion = tenantInfo.SettingsVersion;
                        var serverVersion   = _settingsCache.GetSettingsIfCached(tenantId)?.Version;

                        if (serverVersion == null || serverVersion != databaseVersion)
                        {
                            // Update the cache
                            var settings = await GeneralSettingsService.LoadSettingsForClient(_appRepo, cancellation);

                            if (!cancellation.IsCancellationRequested)
                            {
                                _settingsCache.SetSettings(tenantId, settings);
                            }
                        }
                    }

                    // (7) If any version headers are supplied: examine their freshness
                    {
                        // Permissions
                        var clientVersion = context.HttpContext.Request.Headers["X-Permissions-Version"].FirstOrDefault();
                        if (!string.IsNullOrWhiteSpace(clientVersion))
                        {
                            var databaseVersion = userInfo.PermissionsVersion;
                            context.HttpContext.Response.Headers.Add("x-permissions-version",
                                                                     clientVersion == databaseVersion ? Constants.Fresh : Constants.Stale);
                        }
                    }

                    {
                        // User Settings
                        var clientVersion = context.HttpContext.Request.Headers["X-User-Settings-Version"].FirstOrDefault();
                        if (!string.IsNullOrWhiteSpace(clientVersion))
                        {
                            var databaseVersion = userInfo.UserSettingsVersion;
                            context.HttpContext.Response.Headers.Add("x-user-settings-version",
                                                                     clientVersion == databaseVersion ? Constants.Fresh : Constants.Stale);
                        }
                    }

                    {
                        // Definitions
                        var clientVersion = context.HttpContext.Request.Headers["X-Definitions-Version"].FirstOrDefault();
                        if (!string.IsNullOrWhiteSpace(clientVersion))
                        {
                            var databaseVersion = tenantInfo.DefinitionsVersion;
                            context.HttpContext.Response.Headers.Add("x-definitions-version",
                                                                     clientVersion == databaseVersion ? Constants.Fresh : Constants.Stale);
                        }
                    }
                    {
                        // Settings
                        var clientVersion = context.HttpContext.Request.Headers["X-Settings-Version"].FirstOrDefault();
                        if (!string.IsNullOrWhiteSpace(clientVersion))
                        {
                            var databaseVersion = tenantInfo.SettingsVersion;
                            context.HttpContext.Response.Headers.Add("x-settings-version",
                                                                     clientVersion == databaseVersion ? Constants.Fresh : Constants.Stale);
                        }
                    }

                    // Call the Action itself
                    await next();
                }
                catch (TaskCanceledException)
                {
                    context.Result = new OkResult();
                    return;
                }
                catch (MultitenancyException ex)
                {
                    // If the tenant Id is not provided cut the pipeline short and return a Bad Request 400
                    context.Result = new BadRequestObjectResult(ex.Message);
                    return;
                }
                catch (BadRequestException ex)
                {
                    // If the tenant Id is not provided cut the pipeline short and return a Bad Request 400
                    context.Result = new BadRequestObjectResult(ex.Message);
                    return;
                }
                catch (Exception ex)
                {
                    // TODO: Return to logging and 500 status code
                    context.Result = new BadRequestObjectResult(ex.GetType().Name + ": " + ex.Message);
                    //_logger.LogError(ex.Message);
                    //context.Result = new StatusCodeResult(StatusCodes.Status500InternalServerError);

                    return;
                }
            }