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.")); }); }
protected override void ConfigureDefaultRuleSet() { RuleFor(sfe => sfe.CustomerIdentifier) .NotEmpty() .WithState(e => new ScimError( HttpStatusCode.BadRequest, ScimErrorType.InvalidValue, ScimErrorDetail.AttributeRequired("customerIdentifier"))); }
protected override void ConfigureDefaultRuleSet() { RuleFor(tenant => tenant.Name) .NotEmpty() .WithState(t => new ScimError( HttpStatusCode.BadRequest, ScimErrorType.InvalidValue, ScimErrorDetail.AttributeRequired("name"))); }
protected override void ConfigureDefaultRuleSet() { base.ConfigureDefaultRuleSet(); RuleFor(u => u.NickName) .NotNull() .WithState(u => new ScimError( HttpStatusCode.BadRequest, ScimErrorType.InvalidValue, ScimErrorDetail.AttributeRequired("nickName"))); }
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))); }
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))); }
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)); }
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"))) } }); }); }
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); }