Skip to content

Commit

Permalink
Add filter query builder for users.
Browse files Browse the repository at this point in the history
  • Loading branch information
omer-iqbal committed Jun 21, 2015
1 parent 22d7dbf commit ae1f710
Show file tree
Hide file tree
Showing 13 changed files with 648 additions and 60 deletions.
12 changes: 12 additions & 0 deletions AadGraphApiHelper/AadGraphApiHelper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,16 @@
<ItemGroup>
<Compile Include="AadEnvironment.cs" />
<Compile Include="ApplicationType.cs" />
<Compile Include="GraphApiEntityType.cs" />
<Compile Include="GraphApiUrlFilterComponent.cs" />
<Compile Include="GraphApiProperty.cs" />
<Compile Include="Names.cs" />
<Compile Include="QueryBuilderForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="QueryBuilderForm.Designer.cs">
<DependentUpon>QueryBuilderForm.cs</DependentUpon>
</Compile>
<Compile Include="TenantCredentialSet.cs" />
<Compile Include="AadEnvironmentSet.cs">
<SubType>Code</SubType>
Expand Down Expand Up @@ -113,6 +122,9 @@
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<EmbeddedResource Include="QueryBuilderForm.resx">
<DependentUpon>QueryBuilderForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="StringResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>StringResources.Designer.cs</LastGenOutput>
Expand Down
82 changes: 82 additions & 0 deletions AadGraphApiHelper/GraphApiEntityType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AadGraphApiHelper
{
// Ideally we would get this from the service metadata. However, certain
// information, such as which properties are filterable, is not available
// in the service metadata, and needs to be hardcoded. Then, instead of
// splitting information (i.e. some hardcoded, and some
// retrieved from metadata), all the information is hardcoded here.
//
// TODO: The only place where getting the service metadata will be useful
// would be to determine which properties are available in which api versions.
internal class GraphApiEntityType
{
private static readonly GraphApiEntityType users;

static GraphApiEntityType()
{
users = new GraphApiEntityType(Names.Users)
{
Properties = new List<GraphApiProperty>
{
new GraphApiProperty(Names.AccountEnabled, typeof(bool), true),
new GraphApiProperty(Names.City, typeof(string), true),
new GraphApiProperty(Names.Country, typeof(string), true),
new GraphApiProperty(Names.DeletionTimeStamp, typeof(DateTime), false),
new GraphApiProperty(Names.Department, typeof(string), true),
new GraphApiProperty(Names.DirSyncEnabled, typeof(bool), true),
new GraphApiProperty(Names.DisplayName, typeof(string), true),
new GraphApiProperty(Names.FacsimileTelephoneNumber, typeof(string), false),
new GraphApiProperty(Names.GivenName, typeof(string), true),
new GraphApiProperty(Names.ImmutableId, typeof(string), true),
new GraphApiProperty(Names.JobTitle, typeof(string), true),
new GraphApiProperty(Names.LastDirSyncTime, typeof(DateTime), true),
new GraphApiProperty(Names.Mail, typeof(string), true),
new GraphApiProperty(Names.MailNickName, typeof(string), true),
new GraphApiProperty(Names.Mobile, typeof(string), false),
new GraphApiProperty(Names.ObjectId, typeof(Guid), false), // This is technically filterable, but only with eq operator
new GraphApiProperty(Names.ObjectType, typeof(string), false),
new GraphApiProperty(Names.OnPremisesSecurityIdentifier, typeof(string), false),
new GraphApiProperty(Names.OtherMails, typeof(ICollection<string>), true),
new GraphApiProperty(Names.PasswordPolicies, typeof(string), false),
new GraphApiProperty(Names.PhysicalDeliveryOfficeName, typeof(string), false),
new GraphApiProperty(Names.PostalCode, typeof(string), false),
new GraphApiProperty(Names.PreferredLanguage, typeof(string), false),
new GraphApiProperty(Names.ProxyAddresses, typeof(ICollection<string>), true),
new GraphApiProperty(Names.SipProxyAddress, typeof(string), false),
new GraphApiProperty(Names.State, typeof(string), true),
new GraphApiProperty(Names.StreetAddress, typeof(string), false),
new GraphApiProperty(Names.Surname, typeof(string), true),
new GraphApiProperty(Names.TelephoneNumber, typeof(string), false),
new GraphApiProperty(Names.UsageLocation, typeof(string), true),
new GraphApiProperty(Names.UserPrincipalName, typeof(string), true),
new GraphApiProperty(Names.UserType, typeof(string), true)
}
};
}

private GraphApiEntityType(string name)
{
if (String.IsNullOrEmpty(name))
{
throw new ArgumentException(StringResources.NameCannotBeNullOrWhiteSpace, "name");
}

this.Name = name;
}

public string Name { get; private set; }

public IReadOnlyCollection<GraphApiProperty> Properties { get; private set; }

public static GraphApiEntityType Users
{
get { return users; }
}
}
}
121 changes: 103 additions & 18 deletions AadGraphApiHelper/GraphApiUrlBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AadGraphApiHelper
{
Expand All @@ -16,6 +19,13 @@ internal class GraphApiUrlBuilder

public TenantCredential TenantCredential { get; set; }

private IList<GraphApiUrlFilterComponent> filterComponents = new List<GraphApiUrlFilterComponent>();

public IList<GraphApiUrlFilterComponent> FilterComponents
{
get { return this.filterComponents; }
}

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
Expand All @@ -24,37 +34,112 @@ internal class GraphApiUrlBuilder
/// </returns>
public override string ToString()
{
return this.Url;
return this.CreateUrl();
}

public string CreateUrl()
{
if (this.Environment == null ||
this.TenantCredential == null ||
String.IsNullOrWhiteSpace(this.ResourceFirst) ||
String.IsNullOrWhiteSpace(this.ApiVersion))
{
return null;
}

UriBuilder uriBuilder = new UriBuilder(this.Environment.GraphResourceUrl);
uriBuilder.Path = this.TenantCredential.Tenant + '/' + this.ResourceFirst;
if (!String.IsNullOrWhiteSpace(this.ResourceId))
{
uriBuilder.Path += '/' + this.ResourceId;
}

if (!String.IsNullOrWhiteSpace(this.ResourceSecond))
{
uriBuilder.Path += '/' + this.ResourceSecond;
}

IDictionary<string, string> queryPairs = new Dictionary<string, string>();

string filterQuery = this.CreateFilterQuery();
if (!String.IsNullOrWhiteSpace(filterQuery))
{
queryPairs["$filter"] = filterQuery;
}

queryPairs["api-version"] = this.ApiVersion;

uriBuilder.Query = ConvertQueryCollection(queryPairs);
uriBuilder.Port = -1;
return uriBuilder.ToString();
}

public string Url
private static string ConvertQueryCollection(IDictionary<string,string> queryPairs)
{
get
if (queryPairs == null || queryPairs.Count == 0)
{
if (this.Environment == null ||
this.TenantCredential == null ||
String.IsNullOrWhiteSpace(this.ResourceFirst) ||
String.IsNullOrWhiteSpace(this.ApiVersion))
return null;
}

StringBuilder stringBuilder = new StringBuilder();
foreach (KeyValuePair<string, string> pair in queryPairs)
{
if (stringBuilder.Length > 0)
{
return null;
stringBuilder.Append(@"&");
}

UriBuilder uriBuilder = new UriBuilder(this.Environment.GraphResourceUrl);
uriBuilder.Query = "api-version=" + this.ApiVersion;
uriBuilder.Path = this.TenantCredential.Tenant + '/' + this.ResourceFirst;
if (!String.IsNullOrWhiteSpace(this.ResourceId))
stringBuilder.AppendFormat("{0}={1}", pair.Key, pair.Value);
}

return stringBuilder.ToString();
}

private string CreateFilterQuery()
{
const string LogicalOperatorFormat = @" {0} ";
const string AnyEqString = @"{0}/any(p:p {1} '{2}')";
const string FunctionOperator = @"{0}({1},'{2}')";
const string OperatorForStrings = @"{0} {1} '{2}'";
const string OperatorForGuids = @"{0} {1} guid'{2}'";
const string OperatorForOthers = @"{0} {1} {2}";
StringBuilder filterQueryBuilder = new StringBuilder();
foreach (GraphApiUrlFilterComponent filterComponent in this.filterComponents)
{
string andOr = filterComponent.LogicalOperator;
string op = filterComponent.ComparisonOperator;
string propertyName = filterComponent.PropertyName;
string value = filterComponent.Value;
GraphApiProperty property = GraphApiEntityType.Users.Properties.Single(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));

if (filterQueryBuilder.Length > 0)
{
uriBuilder.Path += '/' + this.ResourceId;
filterQueryBuilder.AppendFormat(LogicalOperatorFormat, andOr);
}

if (!String.IsNullOrWhiteSpace(this.ResourceSecond))
if (op.Equals(Names.StartsWithOperator, StringComparison.OrdinalIgnoreCase))
{
uriBuilder.Path += '/' + this.ResourceSecond;
filterQueryBuilder.AppendFormat(FunctionOperator, Names.StartsWithOperator, propertyName, value);
}
else if (op.Equals(Names.AnyEqualsOperator, StringComparison.OrdinalIgnoreCase))
{
filterQueryBuilder.AppendFormat(AnyEqString, propertyName, Names.EqualToOperator, value);
}
else if (property.Type == typeof(Guid))
{
filterQueryBuilder.AppendFormat(OperatorForGuids, propertyName, op, value);
}
else if (property.Type == typeof(bool) || property.Type == typeof(int))
{
filterQueryBuilder.AppendFormat(OperatorForOthers, propertyName, op, value);
}
else
{
filterQueryBuilder.AppendFormat(OperatorForStrings, propertyName, op, value);
}

uriBuilder.Port = -1;
return uriBuilder.ToString();
}

return filterQueryBuilder.ToString();
}
}
}
23 changes: 23 additions & 0 deletions AadGraphApiHelper/GraphApiUrlFilterComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;

namespace AadGraphApiHelper
{
internal class GraphApiUrlFilterComponent
{
private static string[] allowedLogicalOperators = { Names.AndOperator, Names.OrOperator };

public static string[] AllowedLogicalOperators
{
get { return allowedLogicalOperators; }
}

public string LogicalOperator { get; set; }

public string PropertyName { get; set; }

public string ComparisonOperator { get; set; }

public string Value { get; set; }
}
}
Loading

0 comments on commit ae1f710

Please sign in to comment.