예제 #1
0
        /// <summary>
        /// Applies filtering to a multiple-item request.
        /// </summary>
        /// <param name="relevantClaims">The subset of the caller's claims that are relevant for the authorization decision.</param>
        /// <param name="authorizationContext">The authorization context.</param>
        /// <returns>The list of filters to be applied to the query for authorization.</returns>
        public IReadOnlyList <AuthorizationFilterDetails> GetAuthorizationFilters(
            IEnumerable <Claim> relevantClaims,
            EdFiAuthorizationContext authorizationContext)
        {
            EnsureDependencies();

            // Find a generated context data provider for the entity
            var authorizationContextDataProvider  = RelationshipsAuthorizationContextDataProviderFactory.GetProvider(authorizationContext.Type);
            var authorizationContextPropertyNames = authorizationContextDataProvider.GetAuthorizationContextPropertyNames();

            var authorizationSegments = GetAuthorizationSegments(relevantClaims, authorizationContextPropertyNames, null);

            // Convert segments to general-purpose filters
            return(AuthorizationSegmentsToFiltersConverter.Convert(authorizationSegments));
        }
예제 #2
0
        public async Task AuthorizeSingleItemAsync(IEnumerable <Claim> relevantClaims, EdFiAuthorizationContext authorizationContext,
                                                   CancellationToken cancellationToken)
        {
            EnsureDependencies();

            // Find a generated context data provider for the entity
            var authorizationContextDataProvider =
                RelationshipsAuthorizationContextDataProviderFactory.GetProvider(authorizationContext.Data.GetType());

            var authorizationContextPropertyNames = authorizationContextDataProvider.GetAuthorizationContextPropertyNames();

            // Extract the context data for making the final authorization decision.
            TContextData contextData = authorizationContextDataProvider.GetContextData(authorizationContext.Data);

            // Convert any EducationOrganizationIds into their concrete types
            var concreteContextData =
                _concreteEducationOrganizationIdAuthorizationContextDataTransformer.GetConcreteAuthorizationContextData(contextData);

            var authorizationSegments = GetAuthorizationSegments(relevantClaims, authorizationContextPropertyNames, concreteContextData);

            var multipleSegmentsErrorMessages = new List <string>();

            foreach (var segment in authorizationSegments)
            {
                var isSubjectEndpointReachableFromAnyClaimEndpointsInSegment = false;
                var errorMessages = new List <string>();

                foreach (var name in segment.ClaimsEndpoints.Select(s => s.Name))
                {
                    // NOTE: Embedded convention (trimming Id suffix to get EdOrg type)
                    string claimEducationOrganizationType = name.TrimSuffix("Id");

                    // Get a list of identifiers that are not accessible from the claim's associated EdOrg
                    var graph = _educationOrganizationHierarchy.Value;

                    var inaccessibleIdentifierNames = graph
                                                      .Vertices
                                                      .Except(graph.GetDescendantsOrSelf(claimEducationOrganizationType))
                                                      .Select(edOrgType => edOrgType + "Id") // NOTE: Embedded convention (adding Id suffix to EdOrg type)
                                                      .ToList();

                    if (inaccessibleIdentifierNames.Any(s => s.Equals(segment.SubjectEndpoint.Name)))
                    {
                        errorMessages.Add($"Authorization denied.  The claims associated with an identifier of '{name}' " +
                                          $"cannot be used to authorize a request associated with an identifier of '{segment.SubjectEndpoint.Name}'.");;
                    }
                    else
                    {
                        isSubjectEndpointReachableFromAnyClaimEndpointsInSegment = true;
                    }
                }

                if (!isSubjectEndpointReachableFromAnyClaimEndpointsInSegment)
                {
                    multipleSegmentsErrorMessages.AddRange(errorMessages);
                }
            }

            // Validate all segments before throwing an exception if one or more segments are invalid.
            if (multipleSegmentsErrorMessages.Any())
            {
                throw new EdFiSecurityException(string.Join(" ", multipleSegmentsErrorMessages));
            }

            var inlineAuthorizationResults = PerformInlineClaimsAuthorizations();

            // Check for state where no more segments remain to be authorized
            if (!inlineAuthorizationResults.SegmentsStillRequiringAuthorization.Any())
            {
                // If we got to this point and did not perform any inline authorizations, there's nothing that can authorize
                if (!inlineAuthorizationResults.InlineAuthorizationOccurred)
                {
                    // NOTE: It seems like this check and exception could be moved to right after the call to GetAuthorizationSegments,
                    // which would eliminate the need for tracking the InlineAuthorizationOccurred in the inline authorization results.
                    throw new NotSupportedException(
                              "Relationship-based authorization could not be performed on the request because there were no authorization segments defined indicating the resource shouldn't be authorized with a relationship-based strategy.");
                }

                // Inline authorization was performed, was sufficient.
                return;
            }

            // Execute authorization
            await AuthorizationSegmentsVerifier.VerifyAsync(inlineAuthorizationResults.SegmentsStillRequiringAuthorization, cancellationToken);

            InlineAuthorizationResults PerformInlineClaimsAuthorizations()
            {
                // Create a list to store segments that have been locally authorized (because claim and entity values are the same type and are equal)
                var subsequentAuthorizationSegments = new List <ClaimsAuthorizationSegment>();

                bool inlineAuthorizationOccurred = false;

                foreach (var claimsAuthorizationSegment in authorizationSegments)
                {
                    var subjectEndpointWithValue = claimsAuthorizationSegment.SubjectEndpoint as AuthorizationSegmentEndpointWithValue;

                    // This should never happen
                    if (subjectEndpointWithValue == null)
                    {
                        throw new Exception(
                                  "The subject endpoint association for a single-item claims authorization check did not have a value available from context.");
                    }

                    // Segment can possibly be authorized here using value if any of the claims are of the same type and value as the target

                    // Find all the claims endpoint (values) on this segment that *could* be used to authorize the segment
                    var inlinableClaimsEndpoints =
                        claimsAuthorizationSegment.ClaimsEndpoints
                        .Where(x => x.Name.EqualsIgnoreCase(subjectEndpointWithValue.Name))
                        .ToList();

                    var nonInlinableClaimsEndpoints =
                        claimsAuthorizationSegment.ClaimsEndpoints
                        .Where(x => !x.Name.EqualsIgnoreCase(subjectEndpointWithValue.Name));

                    //If we found any claim values that *could* authorize the current segment...
                    if (inlinableClaimsEndpoints.Any())
                    {
                        // Do we have any that actually *do* authorize the segment?
                        if (inlinableClaimsEndpoints.Any(x => x.Value.Equals(subjectEndpointWithValue.Value)))
                        {
                            inlineAuthorizationOccurred = true;
                            continue;
                        }

                        // The claims endpoints we checked couldn't authorize this segment.
                        // If there are not any others to check (i.e. using relationships in the database), then we should preemptively fail authorization now.
                        if (!nonInlinableClaimsEndpoints.Any())
                        {
                            throw new EdFiSecurityException(
                                      $"Authorization denied.  Access to the requested '{subjectEndpointWithValue.Name}' was denied.");
                        }

                        // We found claim value(s) for inlining the authorization check, but it failed to authorize and should not be retried with the database.
                        // Therefore, create a new authorization segment that excludes these specific claim endpoints to allow the others to be checked through database relationships
                        subsequentAuthorizationSegments.Add(new ClaimsAuthorizationSegment(
                                                                nonInlinableClaimsEndpoints.ToArray(),
                                                                claimsAuthorizationSegment.SubjectEndpoint,
                                                                claimsAuthorizationSegment.AuthorizationPathModifier));
                    }
                    else
                    {
                        //The segment could not be authorized inline, so add it for subsequent authorization
                        subsequentAuthorizationSegments.Add(claimsAuthorizationSegment);
                    }
                }

                // Continue with other rules that are not referencing the same types of values available on the claim (e.g. LEA Id to LEA Id)
                return(new InlineAuthorizationResults(subsequentAuthorizationSegments, inlineAuthorizationOccurred));
            }
        }