Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ae1adee
Remove 'In' as operator -custom test method ordering
nianton Jun 5, 2018
731a392
Merge
nianton Jun 19, 2018
3cceadc
Added constructor overload to outsource authentication to external co…
JonasSyrstad Nov 13, 2018
72df97c
added missing field and added unit tests
JonasSyrstad Nov 13, 2018
9d94c47
Merge branch 'master' into pr/1-added-constructor-overload-to-outsour…
nianton Nov 13, 2018
7a1eef7
Added two new properties on user based on the metadata (userState, us…
nianton Nov 13, 2018
c91e16d
Release 1.2.4 -package update
nianton Nov 13, 2018
ea06cb3
(docs) Updated README.md
nianton Nov 13, 2018
040a7e2
Package versioning 1.2.5
nianton Nov 13, 2018
50c57ef
Merge branch 'master' of https://github.com/nianton/GraphLite
nianton Nov 13, 2018
e5fe375
Update README.md
nianton Nov 13, 2018
dbf52ee
Refactored authentication callback
JonasSyrstad Nov 14, 2018
ab3f2c2
fixed doc
JonasSyrstad Nov 14, 2018
7a6e93d
Fixed 2 of the unit tests
JonasSyrstad Nov 14, 2018
57e5557
Merge branch 'master' into AddAuthorizationCallbackOption
JonasSyrstad Nov 14, 2018
3c4d78c
This issue was already taken care off in the master branch.
nianton Nov 14, 2018
38b6485
Introduction of IAuthProvider, slight refactoring. Note: some documen…
nianton Nov 15, 2018
ea22514
Merge from Pull Request along with some slight refactoring and tidyin…
nianton Nov 15, 2018
501a071
Minor tidying up changes
nianton Nov 15, 2018
b22e1b0
(docs) Documentation update, (src) Introduction on a constant for Rep…
nianton Nov 15, 2018
166d22f
Package 1.3.0
nianton Nov 15, 2018
6830b22
Update README.md
nianton Nov 15, 2018
a194cbe
Update xunit on test project
nianton Nov 16, 2018
31e5db3
Converted async tests using async/await signature (possible issue wit…
nianton Nov 16, 2018
41cfec1
More tests converted to async
nianton Nov 16, 2018
7b4c823
More async fixes in the tests
nianton Nov 16, 2018
b783eb7
Simplified tests, new tests for client initialization (credentials an…
nianton Nov 16, 2018
6728bc4
Azure Pipelines was configured and related status icon was added on R…
nianton Nov 16, 2018
97277d7
Minor changes, helper method to get simple property name from a B2C e…
nianton Nov 16, 2018
a957088
Update README.md -added logo
nianton Nov 17, 2018
b206de9
Update README.md
nianton Nov 17, 2018
e0f6e9e
Test refactoring to skip potentially non existing custom property, sh…
nianton Nov 17, 2018
21933ea
Merge branch 'master' of https://github.com/nianton/GraphLite
nianton Nov 17, 2018
79d9818
GraphApiClient has a strongly typed update version, separate generate…
nianton Nov 18, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@

[![NuGet](https://img.shields.io/nuget/dt/GraphLite.svg)](https://www.nuget.org/packages/GraphLite/)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)
[![Build status](https://dev.azure.com/nianton/GraphLite/_apis/build/status/GraphLite-CI)](https://dev.azure.com/nianton/GraphLite/_build/latest?definitionId=1)

This is a lightweight Graph API client for .NET (Framework 4.5 and Standard 2.0) for the user management and reporting needs of an Active Directory B2C Client tenant.

More detailed documentation on: **https://nianton.github.io/GraphLite**

Updates in version **1.3.0**
> * Ability to construct a GraphApiClient with an external authentication callback was added, when relying on an external library for AAD authentication like ADAL.

Updates in version **1.2.5**
> * Added 2 new properties to User (UserState and UserStateChangedOn) -new in AAD B2C

Updates in version **1.2.2**
> * Added support for .NET 4.6.1 (less dependencies)

Updates in version **1.2.1**
> * Added support for .NET 4.5
> * Targets .NET 4.5 and NetStandard 2.0
Expand Down
14 changes: 14 additions & 0 deletions build/buildTestConfig.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
param (
[string]$applicationId = $(throw "-applicationId is required."),
[string]$applicationSecret = $(throw "-applicationSecret is required."),
[string]$tenant = $(throw "-tenant is required."),
[string]$extensionProperty = ""
)

$settings = Get-Content ..\src\GraphLite.Tests\appsettings.stub.json
$settings = $settings.Replace("<APPLICATION_ID>", $applicationId)
$settings = $settings.Replace("<APPLICATION_SECRET>", $applicationSecret)
$settings = $settings.Replace("<TENANT>", $tenant.Replace(".onmicrosoft.com", ""))
$settings = $settings.Replace("<EXTENSION_PROPERTY>", $extensionProperty)

Set-Content -Value $settings -Path ..\src\GraphLite.Tests\appsettings.json
27 changes: 24 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ GraphLite is a lightweight GraphAPI client for .NET (.NET standard package) for
After creating the AAD B2C tenant on Azure Portal, in order to use the GraphAPI to manage users and access reports an application has to be registered on the Azure Active Directory of the created tenant (NOT on the B2C menu item).

### Authentication
After creating the application and generating the secret key for it, and assigning the necessary rights, we can start using GraphAPI to manage the B2C users.

In order for the client to be able to make calls on the Graph API, will have to acquire an access token on the Azure Active Directory.
The acquisition of the access token is taken care by the GraphApiClient, and you can keep a reference of the GraphApiClient the lifetime of your
application in order to minimize the overhead of acquiring a new access token for each new instance created.

#### Using application credentials
After the creation of an application on Azure Active Directory and generating the secret key for it, and assigning the necessary rights, we can start using GraphAPI to manage the B2C users.
NOTE: For more information about how to register an application on your tenant's AAD follow [the official AAD B2C documentation](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet#register-your-application-in-your-tenant).

```csharp
// Create a client instance
Expand All @@ -15,13 +22,27 @@ var client = new GraphApiClient(
"<ApplicationSecret>",
"<tenantname>.onmicrosoft.com");

// Ensure client's initialization validates two things
// Ensuring client's initialization validates two things
// * authorization credentials provided
// * ensures B2C extension application
// NOTE: this is not necessary though, will be called internally when needed.
await client.EnsureInitAsync();
```

The acquisition of the access token is taken care by the GraphApiClient, and you can keep a reference of it for the lifetime of your application in order to minimize the overhead of acquiring a new access token for each new instance created.
#### Using an external delegate for authentication
After the creation of an application on Azure Active Directory and generating the secret key for it, and assigning the necessary rights, we can start using GraphAPI to manage the B2C users.

```csharp
// Setup the authorization on your actual implementation -the following is just a pseudocode sample.
var externalAuthenticator = ...[your external authentication library, eg: ADAL];
var authorizationCallback = new Func<string, Task<TokenWrapper>>(
async resource => await externalAuthenticator.AcquireTokenAsync(parameters));

// Create a client instance which relies on the external autheticator to provide the access token.
var client = new GraphApiClient(
"<tenantname>.onmicrosoft.com",
authorizationCallback);
```

## [User Operations >>](users)

Expand Down
27 changes: 24 additions & 3 deletions docs/reporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,36 @@ var userCounts = await client.Reporting.GetTenantUserCountSummariesAsync(lastWee

Summary of the daily number of billable authentications over the last 30 days, by day and type of authentication flow.

[TODO:// Documentation]
```csharp
// Get the daily user counts for the last week.
client.Reporting.GetAuthenticationCountSummariesAsync()
```

## Get Daily Authentication Count summaries

[TODO:// Documentation]
Gets the summaries of billable authentications over the last 30 days, by day and type of authentication flow
```csharp
// Get the daily user counts for the last week.
client.Reporting.GetAuthenticationCountSummariesAsync()
```

## Get Mfa Request Count
The number of MFA requests within a time period. If not defined (optional parameters), defaults to the last 30 days.

```csharp
// Gets the billable MFA requests for the last week.
var startTimeStamp = DateTimeOffset.Now.AddDays(-7);
var endTimeStamp = default(DateTimeOffset?);
var mfaRequestCount = await client.Reporting.GetMfaRequestCountAsync(startTimeStamp: startTimeStamp, endTimeStamp: endTimeStamp);
```

## Get Daily MFA Request Counts summaries
Retrive the summaries of billable MFA requests over the last 30 days, by day and type of MFA request.

[TODO:// Documentation]
```csharp
// Get the billable MFA requests for the last 30 days.
var mfaSummaries = await client.Reporting.GetMfaRequestCountSummariesAsync();
```


[<< Go back](./)
6 changes: 3 additions & 3 deletions docs/users.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ var totalCount = 0;
// Optional progress reporting
var progress = new Progress<IList<User>>(pagedUsers =>
{
// Report progress here
// Report progress here, possibly update UI etc
totalCount += pagedUsers.Count;
});

Expand Down Expand Up @@ -83,7 +83,7 @@ var user = new User
}
};

// Supports extended B2C tenant properties e.g. for a property
// Supports extended B2C tenant properties e.g. sample for a property
// 'TaxRegistrationNumber' registered on the tenant:
user.SetExtendedProperty("TaxRegistrationNumber", "120498219");

Expand Down Expand Up @@ -141,7 +141,7 @@ Retrieves a User's thumbnail image data (usually JPEG format).

```csharp
var userObjectId = "<user-id>";
var imageData = new byte[0]; // Thumbnail's byte array
var imageData = File.ReadAllBytes("myavatar.jpg"); // Thumbnail's byte array
var contentType = "image/jpeg";

await client.UserUpdateThumbnailAsync(userObjectId, imageData, contentType);
Expand Down
8 changes: 0 additions & 8 deletions src/GraphLite.Tests/App.config

This file was deleted.

8 changes: 0 additions & 8 deletions src/GraphLite.Tests/App.stub.config

This file was deleted.

69 changes: 69 additions & 0 deletions src/GraphLite.Tests/GraphClientInitializationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;

namespace GraphLite.Tests
{
public class GraphClientInitializationTests
{
private readonly TestsConfig Config = TestsConfig.Create();
private string AuthTokenEndpoint => $"https://login.windows.net/{Config.Tenant}/oauth2/token";

[Fact]
public async Task TestWithCredentials()
{
var client = new GraphApiClient(Config.ApplicationId, Config.ApplicationSecret, Config.Tenant);
await client.EnsureInitAsync();

// If something is wrong with init call (authorization issues etc), exception will be thrown.
Assert.True(true);
}

[Fact]
public async Task TestWithAuthDelegate()
{
var client = new GraphApiClient(Config.Tenant, GetAccessTokenAsync);
await client.EnsureInitAsync();

// If something is wrong with init call (authorization issues etc), exception will be thrown.
Assert.True(true);
}

private async Task<TokenWrapper> GetAccessTokenAsync(string resource)
{
var httpclient = new HttpClient();
var requestMessage = new HttpRequestMessage(HttpMethod.Post, AuthTokenEndpoint);
var contentParameters = new Dictionary<string, string>
{
{ "client_id", Config.ApplicationId },
{ "client_secret", Config.ApplicationSecret },
{ "grant_type", "client_credentials" },
{ "scope", "offline_access" },
{ "resource", resource }
};

var content = new FormUrlEncodedContent(contentParameters);
requestMessage.Content = content;
var responseMessage = await httpclient.SendAsync(requestMessage);
var response = await responseMessage.Content.ReadAsStringAsync();

if (!responseMessage.IsSuccessStatusCode)
{
var errorResponse = JsonConvert.DeserializeObject<ErrorInfo>(response);
throw new GraphAuthException(requestMessage, responseMessage, errorResponse);
}

var authTokenResponse = JsonConvert.DeserializeObject<AuthTokenResponse>(response);
var token = new TokenWrapper
{
Expiry = DateTimeOffset.Now.Add(TimeSpan.FromSeconds(authTokenResponse.ExpiresIn)),
AccessToken = authTokenResponse.AccessToken
};

return token;
}
}
}
58 changes: 31 additions & 27 deletions src/GraphLite.Tests/GraphClientQueryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
using Xunit;

namespace GraphLite.Tests
{
public class GraphClientQueryTests : IClassFixture<TestFixture>
{
[Collection(TestFixtureCollection.Name)]
public class GraphClientQueryTests
{
readonly GraphApiClient _client;
readonly TestFixture _fixture;
private readonly GraphApiClient _client;
private readonly TestClientFixture _fixture;

public GraphClientQueryTests(TestFixture fixture)
public GraphClientQueryTests(TestClientFixture fixture)
{
_fixture = fixture;
_client = fixture.Client;
Expand All @@ -21,7 +22,7 @@ public GraphClientQueryTests(TestFixture fixture)
public void TestQueryInOperator()
{
var userQuery = new ODataQuery<User>()
.WhereIn(u => u.UserPrincipalName, "another@gmail.com", "another2@gmail.com")
.WhereIn(u => u.UserPrincipalName, "another@gmail.com", "another2@gmail.com")
.Top(20)
.OrderBy(u => u.MailNickname);

Expand All @@ -45,65 +46,68 @@ public void TestQueryTwoFilterClauses()
}


[Fact]
public void TestQueryByExtensionProperty()
[SkippableFact]
public async Task TestQueryByExtensionProperty()
{
var userQuery = _client.UserQueryCreateAsync().Result;
var extPropertyName = "TaxRegistrationNumber";
var extPropertyName = _fixture.ExtensionPropertyName;
Skip.If(string.IsNullOrEmpty(extPropertyName), "No extension property defined");

var userQuery = await _client.UserQueryCreateAsync();
userQuery
.WhereExtendedProperty(extPropertyName, "1235453", ODataOperator.Equals)
.Where(u => u.GivenName, "nikos", ODataOperator.GreaterThanEquals)
.Top(20)
.OrderBy(u => u.MailNickname);

var extAppId = _client.GetB2cExtensionsApplicationAsync().Result.AppId;
var extApp = await _client.GetB2cExtensionsApplicationAsync();
var extAppId = extApp.AppId;
var expected = $"$top=20&$orderby=mailNickname&$filter=extension_{extAppId.Replace("-", string.Empty)}_{extPropertyName} eq '1235453' and givenName ge 'nikos'";
var actualDecoded = System.Net.WebUtility.UrlDecode(userQuery.ToString());
Assert.Equal(expected, actualDecoded);
}

[Fact]
public void TestFetchByUserName()
public async Task TestFetchByUserName()
{
var signinName = _fixture.TestUser.SignInNames[0].Value;
var user = _client.UserGetBySigninNameAsync(signinName).Result;
var user = await _client.UserGetBySigninNameAsync(signinName);
Assert.Equal(_fixture.TestUserObjectId, user.ObjectId);
}



[Fact]
public void TestFetchByExtensionProperty()
[SkippableFact]
public async Task TestFetchByExtensionProperty()
{
var extPropertyName = "TaxRegistrationNumber";
var userQuery = _client.UserQueryCreateAsync()
.Result
var extPropertyName = _fixture.ExtensionPropertyName;
Skip.If(string.IsNullOrEmpty(extPropertyName), "No extension property defined");

var userQuery = await _client.UserQueryCreateAsync();
userQuery
.WhereExtendedProperty(extPropertyName, _fixture.TestUser.GetExtendedProperties()[extPropertyName], ODataOperator.Equals);

var users = _client.UserGetListAsync(userQuery).Result;
var users = await _client.UserGetListAsync(userQuery);
Assert.NotEmpty(users.Items);
Assert.Contains(users.Items, item => item.ObjectId == _fixture.TestUserObjectId);
}


[Fact]
[SkippableFact]
public async Task TestFetchByExtensionPropertyGreaterThan()
{
var extPropertyName = "TaxRegistrationNumber";
var extPropertyName = _fixture.ExtensionPropertyName;
Skip.If(string.IsNullOrEmpty(extPropertyName), "No extension property defined");

var userQuery = await _client.UserQueryCreateAsync();
userQuery
.WhereExtendedProperty(extPropertyName, "1", ODataOperator.GreaterThan);
userQuery.WhereExtendedProperty(extPropertyName, "1", ODataOperator.GreaterThan);

await Assert.ThrowsAsync<GraphApiException>(() => _client.UserGetListAsync(userQuery));
}

[Fact]
public void TestFetchByUserNameViaStandardQuery()
public async Task TestFetchByUserNameViaStandardQuery()
{
var signinName = _fixture.TestUser.SignInNames[0].Value;
var query = new ODataQuery<User>().Where(u => u.SignInNames, sn => sn.Value, signinName, ODataOperator.Equals);
var users = _client.UserGetListAsync(query).Result;
var users = await _client.UserGetListAsync(query);
Assert.Single(users.Items);
Assert.Equal(_fixture.TestUserObjectId, users.Items[0].ObjectId);
}
Expand Down
Loading