protected override void ConfigureCreateRuleSet()
        {
            When(user => !string.IsNullOrWhiteSpace(user.UserName),
                 () =>
            {
                RuleFor(user => user.UserName)
                .MustAsync(async(userName, token) => await _UserRepository.IsUserNameAvailable(userName))
                .WithState(u =>
                           new ScimError(
                               HttpStatusCode.Conflict,
                               ScimErrorType.Uniqueness,
                               ScimErrorDetail.AttributeUnique("userName")));
            });

            When(user => user.Password != null,
                 () =>
            {
                RuleFor(user => user.Password)
                .MustAsync(async(password, token) => await _PasswordManager.MeetsRequirements(password))
                .WithState(u =>
                           new ScimError(
                               HttpStatusCode.BadRequest,
                               ScimErrorType.InvalidValue,
                               "The attribute 'password' does not meet the security requirements set by the provider."));
            });
        }
Beispiel #2
0
 protected override void ConfigureDefaultRuleSet()
 {
     RuleFor(sfe => sfe.CustomerIdentifier)
     .NotEmpty()
     .WithState(e =>
                new ScimError(
                    HttpStatusCode.BadRequest,
                    ScimErrorType.InvalidValue,
                    ScimErrorDetail.AttributeRequired("customerIdentifier")));
 }
Beispiel #3
0
 protected override void ConfigureDefaultRuleSet()
 {
     RuleFor(tenant => tenant.Name)
     .NotEmpty()
     .WithState(t =>
                new ScimError(
                    HttpStatusCode.BadRequest,
                    ScimErrorType.InvalidValue,
                    ScimErrorDetail.AttributeRequired("name")));
 }
Beispiel #4
0
        protected override void ConfigureDefaultRuleSet()
        {
            base.ConfigureDefaultRuleSet();

            RuleFor(u => u.NickName)
            .NotNull()
            .WithState(u =>
                       new ScimError(
                           HttpStatusCode.BadRequest,
                           ScimErrorType.InvalidValue,
                           ScimErrorDetail.AttributeRequired("nickName")));
        }
Beispiel #5
0
        public virtual async Task <IScimResponse <Unit> > DeleteUser(string userId)
        {
            var exists = await _UserRepository.UserExists(userId);

            if (!exists)
            {
                return(new ScimErrorResponse <Unit>(
                           new ScimError(
                               HttpStatusCode.NotFound,
                               detail: ScimErrorDetail.NotFound(userId))));
            }

            await _UserRepository.DeleteUser(userId);

            return(new ScimDataResponse <Unit>(default(Unit)));
        }
Beispiel #6
0
        public virtual async Task <IScimResponse <Unit> > DeleteGroup(string groupId)
        {
            var groupExists = await _GroupRepository.GroupExists(groupId);

            if (!groupExists)
            {
                return(new ScimErrorResponse <Unit>(
                           new ScimError(
                               HttpStatusCode.NotFound,
                               detail: ScimErrorDetail.NotFound(groupId))));
            }

            await _GroupRepository.DeleteGroup(groupId);

            return(new ScimDataResponse <Unit>(default(Unit)));
        }
Beispiel #7
0
        public virtual async Task <IScimResponse <ScimGroup> > RetrieveGroup(string groupId)
        {
            var userRecord = SetResourceVersion(await _GroupRepository.GetGroup(groupId));

            if (userRecord == null)
            {
                return(new ScimErrorResponse <ScimGroup>(
                           new ScimError(
                               HttpStatusCode.NotFound,
                               detail: ScimErrorDetail.NotFound(groupId))));
            }

            // repository populates meta only if it sets Created and/or LastModified
            if (userRecord.Meta == null)
            {
                userRecord.Meta = new ResourceMetadata(ScimConstants.ResourceTypes.Group);
            }

            return(new ScimDataResponse <ScimGroup>(userRecord));
        }
Beispiel #8
0
        public virtual async Task <IScimResponse <ScimUser> > RetrieveUser(string userId)
        {
            var userRecord = await _UserRepository.GetUser(userId);

            if (userRecord == null)
            {
                return(new ScimErrorResponse <ScimUser>(
                           new ScimError(
                               HttpStatusCode.NotFound,
                               detail: ScimErrorDetail.NotFound(userId))));
            }

            // repository populates meta only if it sets Created and/or LastModified
            if (userRecord.Meta == null)
            {
                userRecord.Meta = new ResourceMetadata(ScimConstants.ResourceTypes.User);
            }

            SetResourceVersion(userRecord);

            return(new ScimDataResponse <ScimUser>(userRecord));
        }
        protected override void ConfigureUpdateRuleSet()
        {
            RuleFor(user => user.Id)
            .Immutable(() => ExistingRecord.Id, StringComparer.OrdinalIgnoreCase)
            .WithState(u =>
                       new ScimError(
                           HttpStatusCode.BadRequest,
                           ScimErrorType.Mutability,
                           ScimErrorDetail.AttributeImmutable("id")));

            // Updating a username validation
            When(user =>
                 user.UserName != null &&
                 !user.UserName.Equals(ExistingRecord.UserName, StringComparison.OrdinalIgnoreCase),
                 () =>
            {
                RuleFor(user => user.UserName)
                .MustAsync(async(user, userName, token) => await _UserRepository.IsUserNameAvailable(userName))
                .WithState(user =>
                           new ScimError(
                               HttpStatusCode.Conflict,
                               ScimErrorType.Uniqueness,
                               ScimErrorDetail.AttributeUnique("userName")));
            });

            // Updating a user password
            When(user => user.Password != null && _PasswordManager.PasswordIsDifferent(user.Password, ExistingRecord.Password),
                 () =>
            {
                RuleFor(user => user.Password)
                .MustAsync(async(password, token) => await _PasswordManager.MeetsRequirements(password))
                .WithState(u =>
                           new ScimError(
                               HttpStatusCode.BadRequest,
                               ScimErrorType.InvalidValue,
                               "The attribute 'password' does not meet the security requirements set by the provider."));
            });
        }
        protected override void ConfigureDefaultRuleSet()
        {
            RuleFor(u => u.UserName)
            .NotEmpty()
            .WithState(u =>
                       new ScimError(
                           HttpStatusCode.BadRequest,
                           ScimErrorType.InvalidValue,
                           ScimErrorDetail.AttributeRequired("userName")));

            When(user => !string.IsNullOrWhiteSpace(user.PreferredLanguage),
                 () =>
            {
                RuleFor(user => user.PreferredLanguage)
                .Must(ValidatePreferredLanguage)
                .WithState(u =>
                           new ScimError(
                               HttpStatusCode.BadRequest,
                               ScimErrorType.InvalidValue,
                               "The attribute 'preferredLanguage' is formatted the same " +
                               "as the HTTP Accept-Language header field. (e.g., da, en-gb;q=0.8, en;q=0.7)"));
            });
            When(user => user.ProfileUrl != null,
                 () =>
            {
                RuleFor(user => user.ProfileUrl)
                .Must(uri => uri.IsAbsoluteUri)
                .WithState(u =>
                           new ScimError(
                               HttpStatusCode.BadRequest,
                               ScimErrorType.InvalidValue,
                               "The attribute 'profileUrl' must be a valid absolute URI."));
            });
            When(user => !string.IsNullOrWhiteSpace(user.Locale),
                 () =>
            {
                RuleFor(user => user.Locale)
                .Must(locale =>
                {
                    try
                    {
                        CultureInfo.GetCultureInfo(locale);
                        return(true);
                    }
                    catch (Exception)
                    {
                    }

                    return(false);
                })
                .WithState(u =>
                           new ScimError(
                               HttpStatusCode.BadRequest,
                               ScimErrorType.InvalidValue,
                               "The attribute 'locale' MUST be a valid language tag as defined in [RFC5646]."));
            });
            When(user => user.Emails != null && user.Emails.Any(),
                 () =>
            {
                RuleFor(user => user.Emails)
                .SetCollectionValidator(
                    new GenericExpressionValidator <Email>
                {
                    {
                        email => email.Value,
                        config => config
                        .NotEmpty()
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       ScimErrorDetail.AttributeRequired("email.value")))
                        .EmailAddress()
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       "The attribute 'email.value' must be a valid email as defined in [RFC5321]."))
                    }
                });
            });
            When(user => user.Ims != null && user.Ims.Any(),
                 () =>
            {
                RuleFor(user => user.Ims)
                .SetCollectionValidator(
                    new GenericExpressionValidator <InstantMessagingAddress>
                {
                    {
                        im => im.Value,
                        config => config
                        .NotEmpty()
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       ScimErrorDetail.AttributeRequired("im.value")))
                    }
                });
            });
            When(user => user.PhoneNumbers != null && user.PhoneNumbers.Any(),
                 () =>
            {
                // The value SHOULD be specified according to the format defined
                // in [RFC3966], e.g., 'tel:+1-201-555-0123'.
                RuleFor(user => user.PhoneNumbers)
                .SetCollectionValidator(
                    new GenericExpressionValidator <PhoneNumber>
                {
                    {
                        pn => pn.Value,
                        config => config
                        .NotEmpty()
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       ScimErrorDetail.AttributeRequired("phoneNumber.value")))
                        .Must(PhoneNumbers.PhoneNumberUtil.IsViablePhoneNumber)
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       "The attribute 'phoneNumber.value' must be a valid phone number as defined in [RFC3966]."))
                    }
                });
            });
            When(user => user.Photos != null && user.Photos.Any(),
                 () =>
            {
                RuleFor(user => user.Photos)
                .SetCollectionValidator(
                    new GenericExpressionValidator <Photo>
                {
                    {
                        photo => photo.Value,
                        config => config
                        .NotEmpty()
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       ScimErrorDetail.AttributeRequired("photo.value")))
                        .Must(uri => uri.IsAbsoluteUri)
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       "The attribute 'photo.value' must be a valid absolute URI."))
                    }
                });
            });
            When(user => user.Addresses != null && user.Addresses.Any(),
                 () =>
            {
                RuleFor(user => user.Addresses)
                .SetCollectionValidator(
                    new GenericExpressionValidator <MailingAddress>
                {
                    v => v.When(a => !string.IsNullOrWhiteSpace(a.Country),
                                () =>
                    {
                        v.RuleFor(a => a.Country)
                        .Must(countryCode =>
                        {
                            try
                            {
                                new RegionInfo(countryCode);
                                return(true);
                            }
                            catch
                            {
                            }

                            return(false);
                        })
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       "The attribute 'address.country' must be a valid country code as defined by [ISO3166-1 alpha-2]."));
                    })
                });
            });
            When(user => user.Entitlements != null && user.Entitlements.Any(),
                 () =>
            {
                RuleFor(user => user.Entitlements)
                .SetCollectionValidator(
                    new GenericExpressionValidator <Entitlement>
                {
                    {
                        entitlement => entitlement.Value,
                        config => config
                        .NotEmpty()
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       ScimErrorDetail.AttributeRequired("entitlement.value")))
                    }
                });
            });
            When(user => user.Roles != null && user.Roles.Any(),
                 () =>
            {
                RuleFor(user => user.Roles)
                .SetCollectionValidator(
                    new GenericExpressionValidator <Role>
                {
                    {
                        role => role.Value,
                        config => config
                        .NotEmpty()
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       ScimErrorDetail.AttributeRequired("role.value")))
                    }
                });
            });
        }
Beispiel #11
0
        protected override void ConfigureDefaultRuleSet()
        {
            RuleFor(g => g.DisplayName)
            .NotEmpty()
            .WithState(u =>
                       new ScimError(
                           HttpStatusCode.BadRequest,
                           ScimErrorType.InvalidValue,
                           ScimErrorDetail.AttributeRequired("displayName")));

            // Owin.Scim group membership requires clients to either specify a member.Value & member.Type
            // or a member.Ref URI.
            When(group => group.Members != null && group.Members.Any(),
                 () =>
            {
                RuleFor(group => group.Members)
                .NestedRules(v =>
                {
                    v.When(
                        m =>
                        string.IsNullOrWhiteSpace(m.Value) &&
                        m.Ref == null,
                        () =>
                        v.RuleFor(m => m.Value)
                        .NotEmpty()
                        .WithState(m =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       "To modify group members you must specify either a ('member.value' and 'member.type') combination or a ('member.ref') to a valid resource.")));

                    v.When(
                        m => !string.IsNullOrWhiteSpace(m.Value),
                        () =>
                    {
                        v.RuleFor(m => m.Value)
                        .NotEmpty()
                        .WithState(m =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       ScimErrorDetail.AttributeRequired("member.value")));
                        v.RuleFor(m => m.Type)
                        .NotEmpty()
                        .WithState(m =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidValue,
                                       ScimErrorDetail.AttributeRequired("member.type")))
                        .Must(IsValidMemberType)
                        .WithState(m =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidSyntax,
                                       "The attribute 'member.type' must have a valid value."));
                        v.RuleFor(m => m)
                        .MustAsync(async(member, token) => await IsValidResourceValue(member))
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidSyntax,
                                       "The attribute 'member.value' and 'member.type' must be a valid resource."));
                    });

                    v.When(
                        m => string.IsNullOrWhiteSpace(m.Value),
                        () =>
                    {
                        v.RuleFor(m => m.Ref)
                        .NotNull()
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidSyntax,
                                       "The attribute 'member.$ref' must have a valid a url."))
                        .Must(uri => uri.IsAbsoluteUri)
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidSyntax,
                                       "The attribute 'member.$ref' must have a valid url."));

                        v.RuleFor(m => m)
                        .MustAsync(EnsureMemberReferenceIsValid)
                        .WithState(u =>
                                   new ScimError(
                                       HttpStatusCode.BadRequest,
                                       ScimErrorType.InvalidSyntax,
                                       "The attribute 'member.$ref' must have a valid url."));
                    });
                });
            });
        }
        public override async Task ExecuteBindingAsync(
            ModelMetadataProvider metadataProvider,
            HttpActionContext actionContext,
            CancellationToken cancellationToken)
        {
            var jsonString = await actionContext.Request.Content.ReadAsStringAsync();

            var jsonData   = JObject.Parse(jsonString);
            var schemasKey = jsonData.FindKeyCaseInsensitive(ScimConstants.Schemas.Key);

            if (schemasKey == null)
            {
                throw new ScimException(HttpStatusCode.BadRequest,
                                        ScimErrorDetail.AttributeRequired(ScimConstants.Schemas.Key),
                                        ScimErrorType.InvalidValue);
            }

            var schemasValue = jsonData[schemasKey];

            if (schemasValue == null || !schemasValue.HasValues)
            {
                throw new ScimException(HttpStatusCode.BadRequest,
                                        ScimErrorDetail.AttributeRequired(ScimConstants.Schemas.Key),
                                        ScimErrorType.InvalidValue);
            }

            // determine which concrete resource type to instantiate
            Type schemaType = null;

            foreach (var schemaBindingRule in _ServerConfiguration.SchemaBindingRules)
            {
                if (schemaBindingRule.Predicate(((JArray)schemasValue).ToObject <ISet <string> >(), Descriptor.ParameterType))
                {
                    schemaType = schemaBindingRule.Target;
                }
            }

            if (schemaType == null)
            {
                throw new ScimException(
                          HttpStatusCode.BadRequest,
                          "Unsupported schema.",
                          ScimErrorType.InvalidValue);
            }

            if (!Descriptor.ParameterType.IsAssignableFrom(schemaType))
            {
                throw new ScimException(
                          HttpStatusCode.InternalServerError,
                          string.Format(
                              @"The SCIM server's parameter binding rules resulted in a type 
                          which is un-assignable to the controller action's parameter type. 
                          The action's parameter type is '{0}' but the parameter binding rules 
                          resulted in type '{1}'.",
                              Descriptor.ParameterType,
                              schemaType)
                          .RemoveMultipleSpaces());
            }

            // Enforce the request contains all required extensions for the resource.
            var resourceTypeDefinition = (IScimResourceTypeDefinition)_ServerConfiguration.GetScimTypeDefinition(schemaType);
            var requiredExtensions     = _RequiredResourceExtensionCache.GetOrAdd(resourceTypeDefinition.DefinitionType, resourceType => resourceTypeDefinition.SchemaExtensions.Where(e => e.Required).Select(e => e.Schema));

            if (requiredExtensions.Any())
            {
                foreach (var requiredExtension in requiredExtensions)
                {
                    // you cannot set a required schema extension to null, e.g. !HasValues
                    if (jsonData[requiredExtension] == null || !jsonData[requiredExtension].HasValues)
                    {
                        // the request will be cut short by ModelBindingResponseAttribute and the response below will be returned
                        SetValue(actionContext, null);
                        actionContext.Response = actionContext.Request.CreateResponse(
                            HttpStatusCode.BadRequest,
                            new ScimError(
                                HttpStatusCode.BadRequest,
                                ScimErrorType.InvalidValue,
                                string.Format(
                                    "'{0}' is a required extension for this resource type '{1}'. The extension must be specified in the request content.",
                                    requiredExtension,
                                    _ServerConfiguration.GetSchemaIdentifierForResourceType(schemaType))));
                        return;
                    }
                }
            }

            // When no attributes are specified for projection, the response should contain any attributes whose
            // attribute definition Returned is equal to Returned.Request
            if (actionContext.Request.Method == HttpMethod.Post ||
                actionContext.Request.Method == _Patch ||
                actionContext.Request.Method == HttpMethod.Put)
            {
                var queryOptions = AmbientRequestService.QueryOptions;
                if (!queryOptions.Attributes.Any())
                {
                    // TODO: (DG) if no attributes have been specified, fill the attributes artificially with jsonData keys for attributes defined as Returned.Request
                }
            }

            var resource = JsonConvert.DeserializeObject(
                jsonString,
                schemaType,
                Descriptor
                .Configuration
                .Formatters
                .JsonFormatter
                .SerializerSettings);

            SetValue(actionContext, resource);
        }