Skip to content

Commit e9a995c

Browse files
authored
Adding support for inbound provisioning (#68)
* Fixed missing error resource exceptions and implemented RFC compliant erroe result. * Extended resource metadata to be RFC compliant * Added operators EqualOrLessThan and LessThan * Added user support for metadata * Added filtering support for inbound provisioning * Fixed support for on demand provisioning query * Create SCIM Inbound.postman_collection.json Postman collection for inbound provisioning * Code cleanup
1 parent 11a8d7c commit e9a995c

15 files changed

+1236
-372
lines changed

Microsoft.SCIM.WebHostSample/Provider/InMemoryGroupProvider.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ namespace Microsoft.SCIM.WebHostSample.Provider
99
using System.Threading.Tasks;
1010
using System.Web.Http;
1111
using Microsoft.SCIM;
12-
using Microsoft.SCIM.WebHostSample.Resources;
1312

1413
public class InMemoryGroupProvider : ProviderBase
1514
{
@@ -83,12 +82,12 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
8382

8483
if (null == parameters.AlternateFilters)
8584
{
86-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidParameters);
85+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidParameters);
8786
}
8887

8988
if (string.IsNullOrWhiteSpace(parameters.SchemaIdentifier))
9089
{
91-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidParameters);
90+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidParameters);
9291
}
9392

9493
Resource[] results;
@@ -102,17 +101,17 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
102101
{
103102
if (string.IsNullOrWhiteSpace(queryFilter.AttributePath))
104103
{
105-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidParameters);
104+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidParameters);
106105
}
107106

108107
if (string.IsNullOrWhiteSpace(queryFilter.ComparisonValue))
109108
{
110-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidParameters);
109+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidParameters);
111110
}
112111

113112
if (queryFilter.FilterOperator != ComparisonOperator.Equals)
114113
{
115-
throw new NotSupportedException(SampleServiceResources.UnsupportedComparisonOperator);
114+
throw new NotSupportedException(string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, queryFilter.FilterOperator));
116115
}
117116

118117
if (queryFilter.AttributePath.Equals(AttributeNames.DisplayName))
@@ -128,7 +127,7 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
128127
}
129128
else
130129
{
131-
throw new NotSupportedException(SampleServiceResources.UnsupportedFilterAttributeGroup);
130+
throw new NotSupportedException(string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterAttributePathNotSupportedTemplate, queryFilter.AttributePath));
132131
}
133132
}
134133

@@ -238,17 +237,17 @@ public override Task UpdateAsync(IPatch patch, string correlationIdentifier)
238237

239238
if (null == patch.ResourceIdentifier)
240239
{
241-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidPatch);
240+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidOperation);
242241
}
243242

244243
if (string.IsNullOrWhiteSpace(patch.ResourceIdentifier.Identifier))
245244
{
246-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidPatch);
245+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidOperation);
247246
}
248247

249248
if (null == patch.PatchRequest)
250249
{
251-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidPatch);
250+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidOperation);
252251
}
253252

254253
PatchRequest2 patchRequest =

Microsoft.SCIM.WebHostSample/Provider/InMemoryUserProvider.cs

Lines changed: 154 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ namespace Microsoft.SCIM.WebHostSample.Provider
99
using System.Threading.Tasks;
1010
using System.Web.Http;
1111
using Microsoft.SCIM;
12-
using Microsoft.SCIM.WebHostSample.Resources;
1312

1413
public class InMemoryUserProvider : ProviderBase
1514
{
@@ -44,6 +43,11 @@ public override Task<Resource> CreateAsync(Resource resource, string correlation
4443
throw new HttpResponseException(HttpStatusCode.Conflict);
4544
}
4645

46+
// Update metadata
47+
DateTime created = DateTime.UtcNow;
48+
user.Metadata.Created = created;
49+
user.Metadata.LastModified = created;
50+
4751
string resourceIdentifier = Guid.NewGuid().ToString();
4852
resource.Identifier = resourceIdentifier;
4953
this.storage.Users.Add(resourceIdentifier, user);
@@ -82,71 +86,153 @@ public override Task<Resource[]> QueryAsync(IQueryParameters parameters, string
8286

8387
if (null == parameters.AlternateFilters)
8488
{
85-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidParameters);
89+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidParameters);
8690
}
8791

8892
if (string.IsNullOrWhiteSpace(parameters.SchemaIdentifier))
8993
{
90-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidParameters);
94+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidParameters);
9195
}
9296

93-
Resource[] results;
94-
IFilter queryFilter = parameters.AlternateFilters.SingleOrDefault();
95-
if (queryFilter == null)
96-
{
97-
IEnumerable<Core2EnterpriseUser> allUsers = this.storage.Users.Values;
98-
results =
99-
allUsers.Select((Core2EnterpriseUser user) => user as Resource).ToArray();
100-
101-
return Task.FromResult(results);
102-
}
103-
104-
if (string.IsNullOrWhiteSpace(queryFilter.AttributePath))
105-
{
106-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidParameters);
107-
}
97+
IEnumerable<Resource> results = new List<Core2EnterpriseUser>();
10898

109-
if (string.IsNullOrWhiteSpace(queryFilter.ComparisonValue))
99+
if (parameters.AlternateFilters.Count <= 0)
110100
{
111-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidParameters);
101+
results = this.storage.Users.Values.Select(
102+
(Core2EnterpriseUser user) => user as Resource);
112103
}
113-
114-
if (queryFilter.FilterOperator != ComparisonOperator.Equals)
104+
else
115105
{
116-
throw new NotSupportedException(SampleServiceResources.UnsupportedComparisonOperator);
117-
}
106+
results = new List<Core2EnterpriseUser>();
118107

119-
if (queryFilter.AttributePath.Equals(AttributeNames.UserName))
120-
{
121-
IEnumerable<Core2EnterpriseUser> allUsers = this.storage.Users.Values;
122-
results =
123-
allUsers.Where(
124-
(Core2EnterpriseUser item) =>
125-
string.Equals(
126-
item.UserName,
127-
parameters.AlternateFilters.Single().ComparisonValue,
128-
StringComparison.OrdinalIgnoreCase))
129-
.Select((Core2EnterpriseUser user) => user as Resource).ToArray();
130-
131-
return Task.FromResult(results);
108+
foreach (IFilter queryFilter in parameters.AlternateFilters)
109+
{
110+
IEnumerable<Core2EnterpriseUser> users = this.storage.Users.Values;
111+
112+
IFilter andFilter = queryFilter;
113+
IFilter currentFilter = andFilter;
114+
do
115+
{
116+
if (string.IsNullOrWhiteSpace(andFilter.AttributePath))
117+
{
118+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidParameters);
119+
}
120+
121+
else if (string.IsNullOrWhiteSpace(andFilter.ComparisonValue))
122+
{
123+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidParameters);
124+
}
125+
126+
// UserName filter
127+
else if (andFilter.AttributePath.Equals(AttributeNames.UserName, StringComparison.OrdinalIgnoreCase))
128+
{
129+
if (andFilter.FilterOperator != ComparisonOperator.Equals)
130+
{
131+
throw new NotSupportedException(
132+
string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, andFilter.FilterOperator));
133+
}
134+
135+
users =
136+
users.Where(
137+
item =>
138+
string.Equals(
139+
item.UserName,
140+
andFilter.ComparisonValue,
141+
StringComparison.OrdinalIgnoreCase));
142+
}
143+
144+
// ExternalId filter
145+
else if (andFilter.AttributePath.Equals(AttributeNames.ExternalIdentifier, StringComparison.OrdinalIgnoreCase))
146+
{
147+
if (andFilter.FilterOperator != ComparisonOperator.Equals)
148+
{
149+
throw new NotSupportedException(
150+
string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, andFilter.FilterOperator));
151+
}
152+
153+
users =
154+
users.Where(
155+
item =>
156+
string.Equals(
157+
item.ExternalIdentifier,
158+
andFilter.ComparisonValue,
159+
StringComparison.OrdinalIgnoreCase)).ToList();
160+
}
161+
162+
// Id filter
163+
else if (andFilter.AttributePath.Equals(AttributeNames.Identifier, StringComparison.OrdinalIgnoreCase))
164+
{
165+
if (andFilter.FilterOperator != ComparisonOperator.Equals)
166+
{
167+
throw new NotSupportedException(
168+
string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, andFilter.FilterOperator));
169+
}
170+
171+
users =
172+
users.Where(
173+
item =>
174+
string.Equals(
175+
item.Identifier,
176+
andFilter.ComparisonValue,
177+
StringComparison.OrdinalIgnoreCase)).ToList();
178+
}
179+
180+
// Active filter
181+
else if (andFilter.AttributePath.Equals(AttributeNames.Active, StringComparison.OrdinalIgnoreCase))
182+
{
183+
if (andFilter.FilterOperator != ComparisonOperator.Equals)
184+
{
185+
throw new NotSupportedException(
186+
string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, andFilter.FilterOperator));
187+
}
188+
189+
users =
190+
users.Where(
191+
item =>
192+
item.Active == bool.Parse(andFilter.ComparisonValue)).ToList();
193+
}
194+
195+
// LastModified filter
196+
else if (andFilter.AttributePath.Equals($"{AttributeNames.Metadata}.{AttributeNames.LastModified}", StringComparison.OrdinalIgnoreCase))
197+
{
198+
if (andFilter.FilterOperator == ComparisonOperator.EqualOrGreaterThan)
199+
{
200+
users =
201+
users.Where(
202+
item =>
203+
item.Metadata.LastModified >= DateTime.Parse(andFilter.ComparisonValue).ToUniversalTime()).ToList();
204+
}
205+
else if (andFilter.FilterOperator == ComparisonOperator.EqualOrLessThan)
206+
{
207+
users =
208+
users.Where(
209+
item =>
210+
item.Metadata.LastModified <= DateTime.Parse(andFilter.ComparisonValue).ToUniversalTime()).ToList();
211+
}
212+
else
213+
throw new NotSupportedException(
214+
string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterOperatorNotSupportedTemplate, andFilter.FilterOperator));
215+
}
216+
else
217+
throw new NotSupportedException(
218+
string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionFilterAttributePathNotSupportedTemplate, andFilter.AttributePath));
219+
220+
currentFilter = andFilter;
221+
andFilter = andFilter.AdditionalFilter;
222+
223+
} while (currentFilter.AdditionalFilter != null);
224+
225+
results = results.Union(users.Select((Core2EnterpriseUser user) => user as Resource).ToList());
226+
}
132227
}
133228

134-
if (queryFilter.AttributePath.Equals(AttributeNames.ExternalIdentifier))
229+
if (parameters.PaginationParameters != null)
135230
{
136-
IEnumerable<Core2EnterpriseUser> allUsers = this.storage.Users.Values;
137-
results =
138-
allUsers.Where(
139-
(Core2EnterpriseUser item) =>
140-
string.Equals(
141-
item.ExternalIdentifier,
142-
parameters.AlternateFilters.Single().ComparisonValue,
143-
StringComparison.OrdinalIgnoreCase))
144-
.Select((Core2EnterpriseUser user) => user as Resource).ToArray();
145-
146-
return Task.FromResult(results);
231+
int count = parameters.PaginationParameters.Count.HasValue ? parameters.PaginationParameters.Count.Value : 0;
232+
return Task.FromResult(results.Take(count).ToArray());
147233
}
148-
149-
throw new NotSupportedException(SampleServiceResources.UnsupportedFilterAttributeUser);
234+
else
235+
return Task.FromResult(results.ToArray());
150236
}
151237

152238
public override Task<Resource> ReplaceAsync(Resource resource, string correlationIdentifier)
@@ -163,10 +249,9 @@ public override Task<Resource> ReplaceAsync(Resource resource, string correlatio
163249
throw new HttpResponseException(HttpStatusCode.BadRequest);
164250
}
165251

166-
IEnumerable<Core2EnterpriseUser> exisitingUsers = this.storage.Users.Values;
167252
if
168253
(
169-
exisitingUsers.Any(
254+
this.storage.Users.Values.Any(
170255
(Core2EnterpriseUser exisitingUser) =>
171256
string.Equals(exisitingUser.UserName, user.UserName, StringComparison.Ordinal) &&
172257
!string.Equals(exisitingUser.Identifier, user.Identifier, StringComparison.OrdinalIgnoreCase))
@@ -175,11 +260,20 @@ public override Task<Resource> ReplaceAsync(Resource resource, string correlatio
175260
throw new HttpResponseException(HttpStatusCode.Conflict);
176261
}
177262

178-
if (!this.storage.Users.TryGetValue(user.Identifier, out Core2EnterpriseUser _))
263+
Core2EnterpriseUser exisitingUser = this.storage.Users.Values
264+
.FirstOrDefault(
265+
(Core2EnterpriseUser exisitingUser) =>
266+
string.Equals(exisitingUser.Identifier, user.Identifier, StringComparison.OrdinalIgnoreCase)
267+
);
268+
if (exisitingUser == null)
179269
{
180270
throw new HttpResponseException(HttpStatusCode.NotFound);
181271
}
182272

273+
// Update metadata
274+
user.Metadata.Created = exisitingUser.Metadata.Created;
275+
user.Metadata.LastModified = DateTime.UtcNow;
276+
183277
this.storage.Users[user.Identifier] = user;
184278
Resource result = user as Resource;
185279
return Task.FromResult(result);
@@ -226,17 +320,17 @@ public override Task UpdateAsync(IPatch patch, string correlationIdentifier)
226320

227321
if (null == patch.ResourceIdentifier)
228322
{
229-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidPatch);
323+
throw new ArgumentException(string.Format(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidOperation));
230324
}
231325

232326
if (string.IsNullOrWhiteSpace(patch.ResourceIdentifier.Identifier))
233327
{
234-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidPatch);
328+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidOperation);
235329
}
236330

237331
if (null == patch.PatchRequest)
238332
{
239-
throw new ArgumentException(SampleServiceResources.ExceptionInvalidPatch);
333+
throw new ArgumentException(SystemForCrossDomainIdentityManagementServiceResources.ExceptionInvalidOperation);
240334
}
241335

242336
PatchRequest2 patchRequest =
@@ -251,6 +345,9 @@ public override Task UpdateAsync(IPatch patch, string correlationIdentifier)
251345
if (this.storage.Users.TryGetValue(patch.ResourceIdentifier.Identifier, out Core2EnterpriseUser user))
252346
{
253347
user.Apply(patchRequest);
348+
349+
// Update metadata
350+
user.Metadata.LastModified = DateTime.UtcNow;
254351
}
255352
else
256353
{

0 commit comments

Comments
 (0)