/// <summary> /// Applies the authorization filtering criteria to the query created by the decorated instance. /// </summary> /// <param name="specification">An instance of the entity representing the parameters to the query.</param> /// <param name="queryParameters">The parameter values to apply to the query.</param> /// <returns>The criteria created by the decorated instance.</returns> public ICriteria GetCriteriaQuery(TEntity specification, IQueryParameters queryParameters) { var criteria = _decoratedInstance.GetCriteriaQuery(specification, queryParameters); var authorizationFilters = _authorizationFilterContextProvider.GetFilterContext(); if (authorizationFilters.FirstOrDefault() != null) { // This behavior was introduced to handle support for multiple EdOrg types, but this logic must handle all // authorizations performed. Currently, there are no other authorization strategies that use multiple claim types // so this is functional today, but would need to be revisited if such an authorization strategy was introduced. string[] distinctClaimEndpointNames = authorizationFilters .Select(s => s.ClaimEndpointName) .Distinct() .OrderBy(x => x) .ToArray(); bool hasMultipleClaimEndpoints = distinctClaimEndpointNames.Length > 1; var allFiltersGroupedBySubjectName = authorizationFilters.GroupBy( x => x.SubjectEndpointName, x => x); // ICriterions combined using AND var conjunction = new Conjunction(); foreach (var subjectNameGrouping in allFiltersGroupedBySubjectName) { // ICriterions combined using OR var disjunction = new Disjunction(); bool isSubjectNameAuthorizable = false; var unsupportedAuthorizationFilters = new List <string>(); foreach (var filterDetails in subjectNameGrouping) { IReadOnlyList <Action <ICriteria, Junction, IDictionary <string, object>, JoinType> > applicators; if (!_authorizationCriteriaApplicatorProvider.TryGetCriteriaApplicator( filterDetails.FilterName, typeof(TEntity), out applicators)) { unsupportedAuthorizationFilters.Add(filterDetails.FilterName); continue; } isSubjectNameAuthorizable = true; var filtersBackedByNewAuthViews = new List <string> { "LocalEducationAgencyIdToStudentUSI", "SchoolIdToStudentUSI", "LocalEducationAgencyIdToParentUSI", "ParentUSIToSchoolId", "LocalEducationAgencyIdToStaffUSI", "SchoolIdToStaffUSI" }; // Invoke the filter applicators against the current query foreach (var applicator in applicators) { Dictionary <string, object> parameterValues; if (filtersBackedByNewAuthViews.Contains(filterDetails.FilterName, StringComparer.OrdinalIgnoreCase)) { parameterValues = new Dictionary <string, object> { { "SourceEducationOrganizationId", filterDetails.ClaimValues } }; } else { parameterValues = new Dictionary <string, object> { { filterDetails.ClaimEndpointName, filterDetails.ClaimValues } }; } applicator(criteria, disjunction, parameterValues, hasMultipleClaimEndpoints ? JoinType.LeftOuterJoin : JoinType.InnerJoin); } } if (!isSubjectNameAuthorizable) { if (_logger.IsDebugEnabled) { _logger.Debug($"Unable to authorize access to '{typeof(TEntity).FullName}' because none of the following authorization filters were defined: '{string.Join($"', '", unsupportedAuthorizationFilters)}'."); } throw new EdFiSecurityException( $"Unable to authorize the request because there is no authorization support for associating the " + $"API client's associated claim values (of '{string.Join("', '", distinctClaimEndpointNames)}') with the requested resource ('{typeof(TEntity).Name}')."); } conjunction.Add(disjunction); } criteria.Add(conjunction); } return(criteria); }
/// <summary> /// Applies the authorization filtering criteria to the query created by the decorated instance. /// </summary> /// <param name="specification">An instance of the entity representing the parameters to the query.</param> /// <param name="queryParameters">The parameter values to apply to the query.</param> /// <returns>The criteria created by the decorated instance.</returns> public ICriteria GetCriteriaQuery(TEntity specification, IQueryParameters queryParameters) { var criteria = _decoratedInstance.GetCriteriaQuery(specification, queryParameters); var authorizationFiltering = _authorizationFilterContextProvider.GetFilterContext(); var unsupportedAuthorizationFilters = new HashSet <string>(); // Create the "AND" junction var mainConjunction = new Conjunction(); // Create the "OR" junction var mainDisjunction = new Disjunction(); // If there are multiple authorization strategies with views, we must use left outer joins and null/not null checks var joinType = DetermineJoinType(); bool conjunctionFiltersWereApplied = ApplyAuthorizationStrategiesCombinedWithAndLogic(); bool disjunctionFiltersWereApplied = ApplyAuthorizationStrategiesCombinedWithOrLogic(); ApplyJunctionsToCriteriaQuery(); return(criteria); JoinType DetermineJoinType() { var countOfAuthorizationFiltersWithViewBasedFilters = authorizationFiltering.Count( af => af.Filters.Select(afd => { if (_authorizationFilterDefinitionProvider.TryGetAuthorizationFilterDefinition(afd.FilterName, out var filterDetails)) { return(filterDetails); } ; unsupportedAuthorizationFilters.Add(afd.FilterName); return(null); }) .Where(x => x != null) .OfType <ViewBasedAuthorizationFilterDefinition>() .Any()); return(countOfAuthorizationFiltersWithViewBasedFilters > 1 ? JoinType.LeftOuterJoin : JoinType.InnerJoin); } bool ApplyAuthorizationStrategiesCombinedWithAndLogic() { var andStrategies = authorizationFiltering.Where(x => x.Operator == FilterOperator.And).ToArray(); // Combine 'AND' strategies bool conjunctionFiltersApplied = false; foreach (var andStrategy in andStrategies) { if (!TryApplyFilters(mainConjunction, andStrategy.Filters)) { // All filters for AND strategies must be applied, and if not, this is an error condition throw new Exception($"The following authorization filters are not recognized: {string.Join(" ", unsupportedAuthorizationFilters)}"); } conjunctionFiltersApplied = true; } return(conjunctionFiltersApplied); } bool ApplyAuthorizationStrategiesCombinedWithOrLogic() { var orStrategies = authorizationFiltering.Where(x => x.Operator == FilterOperator.Or).ToArray(); // Combine 'OR' strategies bool disjunctionFiltersApplied = false; foreach (var orStrategy in orStrategies) { var filtersConjunction = new Conjunction(); // Combine filters with 'AND' if (TryApplyFilters(filtersConjunction, orStrategy.Filters)) { mainDisjunction.Add(filtersConjunction); disjunctionFiltersApplied = true; } } // If we have some OR strategies with filters defined, but no filters were applied, this is an error condition if (orStrategies.SelectMany(s => s.Filters).Any() && !disjunctionFiltersApplied) { throw new Exception($"The following authorization filters are not recognized: {string.Join(" ", unsupportedAuthorizationFilters)}"); } return(disjunctionFiltersApplied); } bool TryApplyFilters(Conjunction conjunction, IReadOnlyList <AuthorizationFilterContext> filters) { bool allFiltersCanBeApplied = true; foreach (var filterDetails in filters) { if (!_authorizationFilterDefinitionProvider.TryGetAuthorizationFilterDefinition( filterDetails.FilterName, out var ignored)) { unsupportedAuthorizationFilters.Add(filterDetails.FilterName); allFiltersCanBeApplied = false; } } if (!allFiltersCanBeApplied) { return(false); } bool filtersApplied = false; foreach (var filterDetails in filters) { _authorizationFilterDefinitionProvider.TryGetAuthorizationFilterDefinition( filterDetails.FilterName, out var filterApplicationDetails); var applicator = filterApplicationDetails.CriteriaApplicator; var parameterValues = new Dictionary <string, object> { { filterDetails.ClaimParameterName, filterDetails.ClaimParameterValues } }; // Apply the authorization strategy filter applicator(criteria, conjunction, parameterValues, joinType); filtersApplied = true; } return(filtersApplied); } void ApplyJunctionsToCriteriaQuery() { if (disjunctionFiltersWereApplied) { if (conjunctionFiltersWereApplied) { mainConjunction.Add(mainDisjunction); } else { criteria.Add(mainDisjunction); } } if (conjunctionFiltersWereApplied) { criteria.Add(mainConjunction); } } }