Friday, 23 August 2019

Identity Server 4 (Part 1) Scenarios

IdentityServer4 is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core or that is what it says in the tin. This open source software has been designed to add complementary parts, what I am trying to say is, that this is not a finished product. If you want to integrate it, you have to work on it.
The way it works it is simple but very complex because there is a middleware written, that makes the whole process invisible. So for you, it should be very simple to add but to modify it is a different subject.


So in order to make everything to work smoothly I decided to create different scenarios, to see how Identity works.

Folder Structure: One solution with four projects, where one of them is Identity Server.

Step 1

We create a simple .NET Core console App. This is going to act as a consumer of Identity Server. So it is going to be the guy in charge of calling Identity getting the authentication and then call the API with that token issued by Identity Server.


We can copy all this code in the program.cs, don´t forget to put your own namespace. In this case I have call the project “Console.Consumer.Identity”.

Friday, 16 March 2018

Azure Key Vault, Managing Secrets with AAD

Azure Key vault is a cryptographic key management service based on FIPS-validated Hardware security modules (HSMs). This service is a public Azure service that will, over time, be the trust root for important Microsoft first party services, for third party services seeking to offer higher assurances, and for your own custom line of business (LOB) Azure-hosted applications. It offers an Azure Key Vault service, a SQL Server Connector, and an Cloudlink SecureVM.

The interaction with Azure Key Vault can be done in many ways, from masterkey, powershell, .NET, RESTful api, Java etc.

In the following post I am going to do an end to end of how to create a "Secret" in Azure Key Vault, and retrieve it from the console.

Step 1 - Create a Vault for your keys and secret in Azure




Step 2 - Create the Secret (some kind of connection string or key from a provider), I called it HMAC


Step 3 - Probably the hardest, find all the bits and pieces to retrieve the secret. It happens that in orger to get the "Secret" from Azure Key Vault, you need authentication, this is provided by Azure Active Directory. So the next step will be to register our application (the one will be using Azure Key Vault). So lets go for it:
Azure Active Directory-> App Registrations -> New application registration

Step 4 - It is time to get the Application ID, also called ClientID (For security reasons some info has been removed, but as long you have your ClientID, all will be fine)

Step 5 -The next thing we need is the ClientSecret. Go to Settings->Keys. Put a name in the key and duration. A new key will be generated automatically. That is your ClientSecret !


Step 6 - We almost there with Azure. We have just need to tell Key Vault to give access to ur application from  Azure Active Directory so it can access to the keys.
Home->Your Key Vault->Access Policies



Step 7 - In our application in Azure Active Directory will have to allow Azure Key Vault to access to it. We will go to Azure Active Directory->App registrations-> [Our App Name] -> Settings -> Required Permissions-> Add -> Select API -> Azure Key Vault



Step 8- Delegate permissions to the service.


Step 9 - A JSON file needs to be created for "MasterKey" (the program in charge of retrieving the secret) See below. Save it as azuresecret.json in the folder C:\usr\local\.masterkey\ .

Replace your ClientID for mine
Replace your ClientSecret for mine


{
  "nodeAppName":{
    "clientId":"8586f230-AAAA-40d1-9eeb-574459b35077",
    "clientSecret": "JTqMWnQPOncAAOc11x52oIAlHYp71iCajba5lXSAAok=",
    "vaultUri": "https://my.vault.azure.net/"
  }
}


Step 10 - Install MasterKey:

Step 11 - Execute the file with your key vauly url, this should be the result:


Conclusion
This is a proof of concept will help you to not just understand how Azure Key Vault works behind the scenes, but also to check if the configuration is right.


Thursday, 12 January 2017

Continuous Code Quality with .NET, Sonarqube and Docker

Coming back to the devops world, we have realised that there is a platform is becoming very successful in the Java stack, called Sonarqube. Luckly for .NET developers, Microsoft have decided to lend some of the Microsoft developers to improve the C# side of Sonarqube.

Sonarqube is a product based in a MySql database and a set of rules. This product is free, so if you are a dev I will reconsider to install it in your machine. You will learn good practices, and will have a second opinion of your code. On the top of this, you can develop propel productive code.

We have seen few articles about setting up Sonarqube in Windows for C# but to be honest, we have decided to follow our own way, so instead of installing an Apache server + MySql and all the programs needed, we will be creating an instance of a Docker Sonarqube image. What are the advantages? well, we can spin our own machine whenever we want, and have a configured instance we can carry with us.

So how are we going to do it? To have a different approach than my previous article, we are going to use Kitematic , it is some kind of nice UI for Docker. We assume you have installed Docker for Windows already, so no download Kitematic and install it.

Skip the login and open the program. Go to search and type sonarqube. A list of images should come up, select the official one (the first one)

image

Click in create. It will create an instance of SonarQube, and it will give you an address to access to SonarQube in this case is localhost:32769, bear in mind that SonarQube runs in port 9000 but 9000 in the Docker instance is binded to your local 32769:

image

If the last line looks like this, that means you have SonarQube running

image

Now you can go to your browser and type http://localhost:32769, click in Login and enter as user admin and password admin

image

Now let’s go to create a small windows forms project called MyProject1. We will save it on C:\work\MyProject1\MyProject1\ . The folder content will look like this

image

Time to install the client, Sonar Scanner, which will send our project to SonarQube (it will take one min). Download and unzip the SonarQube Scanner (let's say in "C:\sonar-scanner"). Go “"to “This PC in Windows 10->Right click->Properties” and add in the Environmental Variables area a new Path “C:\sonar-scaner”

image

Then we are going to tell Sonar Scanner where our project lives, so it can analysed it. Open a file called sonar-scanner.properties which lives on C:\sonar-scanner\conf

Edit the file, and add few lines (see below) should look like this after adding the lines:

#Configure here general information about the environment, such as SonarQube DB details for example
#No information about specific project should appear here

#----- Default SonarQube server
#sonar.host.url=
http://localhost:9000

#----- Default source code encoding
#sonar.sourceEncoding=UTF-8

#----- Global database settings (not used for SonarQube 5.2+)
#sonar.jdbc.username=sonar
#sonar.jdbc.password=sonar

#----- PostgreSQL
#sonar.jdbc.url=jdbc:postgresql://localhost/sonar

#----- MySQL
#sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8

#----- Oracle
#sonar.jdbc.url=jdbc:oracle:thin:@localhost/XE

#----- Microsoft SQLServer
#sonar.jdbc.url=jdbc:jtds:sqlserver://localhost/sonar;SelectMethod=Cursor

PROJECT_HOME = C:\work\MyProject1\MyProject1\
#sonar.sourceEncoding=UTF-8
sonar.host.url=http://localhost:327689

sonar.sourceEncoding=UTF-8
sonar.cobol.copy.directories=/copy
sonar.visualstudio.enable=true

sonar.projectKey=MyProject1
sonar.projectName=MyProject1
sonar.projectVersion=1.0
sonar.sources=.
sonar.language=cs
sonar.dotnet.visualstudio.solution.file=MyProject1.sln
sonar.dotnet.excludeGeneratedCode=true
sonar.dotnet.4.0.sdk.directory=C:/Windows/Microsoft.NET/Framework/v4.0.30319
sonar.dotnet.version=4.5.2

Save the file and open CMD with “Run as Administrator”, then navigate to your project folder C:\work\MyProject1\MyProject1\ and type sonar-scanner

image

And you should get something like this

image

Let’s go to check the project, go to http://localhost:32769 and navigate to “Projects->explore projects->Click in MyProject1” you should get a screen like this (I have added a condition for the comments, so it fails):
image

Wednesday, 11 January 2017

Publishing a ASP.NET MVC Site or WebApi to a Docker Machine locally in Windows on Windows Server.

It happens sometimes you will want to develop a single project application. One of those, proof of concepts you do to learn new stuff. The problem comes when you have to stop doing Micky Mouse applications and move to a different world where environments and continuous integration becomes the core of the development.

This is where a new position has risen to help the devs, they are called the DevOps. The issue with DevOps, it is sometimes they have too much work to to, so they can be solving your own problems. Especially if you are doing continuous integration in Azure or Amazon AWS.

This is where Docker becomes a big player, we have a tool it is sophisticated enough to do the job, and it is simple enough to be used by devs. Docker comes from the Java and Linux world, so I thought it will be interesting to try to create a Docker image to run .NET stuff.

So step by step…. the first step will be to create a MVC or WebApi application, ASP.NET 4 or ASP.NET Core whatever you prefer.

image

Step 2: Right click in your project (in my case web) –> Publish…

Step 3: Click on custom and enter DockerDeployment and click OK

image

 

Step 4: In the Publish method with the drop down list select File System and in target location, select c:/docker

image

 

Step 5: Click Next->select release->select Publish

image

 

Step 6: It is time to play with Docker. Install Docker (the beta version) https://docs.docker.com/docker-for-windows/ 
and right click on the task bar, then select “Switch to Windows containers…”

image

 

Step 7: Create a file called “Dockerfile” in c:\docker and paste this (see below). This code will get the code from your deployment and will put it in the image microsoft/aspnet copying the data to the root folder

FROM microsoft/aspnet

WORKDIR /inetpub/wwwroot
COPY .  /inetpub/wwwroot

Step 8: Open powershell with admin permissions, and go to the C:\docker folder and execute this command:
image

This will execute the Dockerfile script and will create a new instance

 

Step 9: It is time to run your instance, just type:

image

 

Step 10: You can list the images by doing a Docker ps . Get the first 3 digits of the CONTAINER ID, in this case 989

image

 

Step 11: It is time to know your URL. just type the following and you will get the URL of your docker machine
docker inspect -f "{{ .NetworkSettings.Networks.nat.IPAddress }}" 989

image

 

Step 12: If you want to access to your local machine you can do things like this (where 989 is the Container Id)

Access to MS-DOS: docker exec -i –t 989 cmd

image

To Create a new Site, use this DockerFile (we called the web Webgenerator):
FROM microsoft/aspnet
SHELL ["powershell"]

RUN Install-WindowsFeature NET-Framework-45-ASPNET ; \  
    Install-WindowsFeature Web-Asp-Net45

COPY WebGenerator WebGenerator 
RUN Remove-WebSite -Name 'Default Web Site'  
RUN New-Website -Name 'WebGenerator' -Port 80 \  
    -PhysicalPath 'c:\WebGenerator' -ApplicationPool '.NET v4.5'
EXPOSE 80

CMD ["ping", "-t", "localhost"]  

Saturday, 7 March 2015

Azure AD Cloud silent Authentication with ADAL and TokenCache encryption

The ADAL library was launched a couple of years ago, luckily people from Microsoft like Vittorio Bertocci, have been working hard to have a library capable of doing authentication between native applications, web applications and web services (Web API). ADAL.js is in beta, it is a full working library but there are some issues need to be solved before we can have a robust library (This is when I am writing my article so March 2015) but Vittorio promised me we will have something working in this side by April 2015.
Unfortunately for me, and due to a project we are releasing before April, there is not choice, so I will have to write a wrapper around my Azure Web Api’s and skip Adal.js until we have v1.0. So you will be asking yourself “what is this guy talking about?”.
Ok, let’s go to start from a "simple" scenario, so you can have an  idea of what I am talking about. I have a Web Application (MVC 5) which is talking with few web services (Web API 2’s). All of them are register in Azure AD, so Azure AD acts as a Black Box, and manage the authentication. In this case Azure AD will take care of the MVC Web Application and the Web API’s Authentication.
So where is the problem? Well if I want to be sure that the Web Application and the Web APis share the same authentication, I will have to use refresh tokens, but because the hard work done by Vittorio’s team, now we can use Silent Tokens, so we don’t have to worry about building a whole system to refresh the tokens, but the only place where we need to worry it is about about where to store the Tokens.
It is quite common to store the tokens in Sessions, but there is a small problem with this, you lose the multi-farm factor plus WebApi’s don’t have sessions, so when you have to talk to them becomes an impossible job.
So what is the solution? store the tokens in a persistent area. If we have a native application we could store the token’s in the file system, but if we have a Web Application we could store them in Blob Storage or Databases.
Let’s go to a scenario, when someone, which is not really a user, and it is trying to get our token to access to our farm or MVC application, manage to access to the database or file system. Well, I think we will be in big trouble here. That person will have at least 20 minutes (Time when our token expires) to hack our system.
So… let’s go to make the things more difficult for this person, let’s go to inject encryption in the tokens, with double key encryption, String and Byte Array.
The following example it is a simple call from a MVC Application to a Web API 2 using silent Authentication.
...//## GETTING THE TOKEN TO BE AUTHORISE string userObjectID = ClaimsPrincipal.Current.FindFirst(userSchema).Value; AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new TokenEncryptedDatabaseCache(userObjectID, "Iamakey"); ClientCredential credential = new ClientCredential(clientId, appKey); AuthenticationResult result = authContext.AcquireTokenSilent(resourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));string authnHeader = "Authorization: Bearer " + result.AccessToken; ...


Now… check the parameter we are passing on  AcquireTokenSilent , it is a TokenCache.

This is the model we use to store the data in the database:


public class PerUserWebCache { [Key] public int EntryId { get; set; } public string WebUserUniqueId { get; set; } public byte[] CacheBits { get; set; } public DateTime LastWrite { get; set; } }
And finally this is the beauty! TokenEncryptedDatabaseCache, which encapsulates the encryption. You will need to create a dbcontext to store the model with the data…which I am going to add as well, so you can save some time.

using System;using System.Collections.Generic;using System.Data.Entity;using System.Linq;using System.Web;using System.Web.Caching;using Microsoft.IdentityModel.Clients.ActiveDirectory;using System.Security.Cryptography;using System.IO; public class TokenEncryptedDatabaseCache : TokenCache { private TokenCacheDataContext db = new TokenCacheDataContext(); string User; private PerUserWebCache Cache; private string Key; /// <summary> /// Contsructor for the TokenEncryptedDatabaseCache class /// </summary> /// <param name="user">Current User</param> /// <param name="key">Key for the encription</param> public TokenEncryptedDatabaseCache(string user, string key) { Key = key; User = user; this.AfterAccess = AfterAccessNotification; this.BeforeAccess = BeforeAccessNotification; this.BeforeWrite = BeforeWriteNotification; //## We check if the user is in our database Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); //## If that is the case we keep it in memory //## We decrypt the token this.Deserialize((Cache == null) ? null : Cache.CacheBits!=null?Decrypt(Cache.CacheBits):null); } /// <summary> /// Method to clean the database /// </summary> public override void Clear() { base.Clear(); foreach (var cacheEntry in db.PerUserCacheList) db.PerUserCacheList.Remove(cacheEntry); db.SaveChanges(); } /// <summary> /// ADAL raise a notification before acces to the cache. /// Notification raised before ADAL accesses the cache. /// This is your chance to update the in-memory copy from the DB, /// if the in-memory version is stale. The token is decrypted. /// </summary> /// <param name="args"></param> void BeforeAccessNotification(TokenCacheNotificationArgs args) { if (Cache == null) { // first time access Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); } else { // retrieve last write from the DB var status = from e in db.PerUserCacheList where (e.WebUserUniqueId == User) select new { LastWrite = e.LastWrite }; // if the in-memory copy is older than the persistent copy if (status.First().LastWrite > Cache.LastWrite) //// read from from storage, update in-memory copy { Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); } } this.Deserialize((Cache == null) ? null : Cache.CacheBits!=null?Decrypt(Cache.CacheBits):null); } /// <summary> ///Notification raised after ADAL accessed the cache. ///If the HasStateChanged flag is set, ADAL changed the content of the cache, ///At this time we encrypt the token and save it. /// </summary> /// <param name="args"></param> void AfterAccessNotification(TokenCacheNotificationArgs args) { // if state changed if (this.HasStateChanged) { Cache = new PerUserWebCache { WebUserUniqueId = User, CacheBits = Encrypt(this.Serialize()), LastWrite = DateTime.Now }; //// update the DB and the lastwrite db.Entry(Cache).State = Cache.EntryId == 0 ? EntityState.Added : EntityState.Modified; db.SaveChanges(); this.HasStateChanged = false; } } void BeforeWriteNotification(TokenCacheNotificationArgs args) { // if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry } /// <summary> /// Encription of the token. /// </summary> /// <param name="DataToEncrypt">Data to be encrypted</param> /// <returns>Data encrypted.</returns> private byte[] Encrypt(byte[] DataToEncrypt) { PasswordDeriveBytes passwordDeriveBytes = new PasswordDeriveBytes(Key, new byte[] { 0x43, 0x87, 0x23, 0x72 }); MemoryStream memoryStream = new MemoryStream(); Aes aes = new AesManaged(); aes.Key = passwordDeriveBytes.GetBytes(aes.KeySize / 8); aes.IV = passwordDeriveBytes.GetBytes(aes.BlockSize / 8); CryptoStream cryptoStream = new CryptoStream(memoryStream,aes.CreateEncryptor(), CryptoStreamMode.Write); cryptoStream.Write(DataToEncrypt, 0, DataToEncrypt.Length); cryptoStream.Close(); return memoryStream.ToArray(); } /// <summary> /// Decryption of the token. /// </summary> /// <param name="DataToDecrypt">Data to be decrypted</param> /// <returns>Data decrypted</returns> private byte[] Decrypt(byte[] DataToDecrypt) { PasswordDeriveBytes passwordDeriveBytes = new PasswordDeriveBytes(Key, new byte[] { 0x43, 0x87, 0x23, 0x72 }); MemoryStream memoryStream = new MemoryStream(); Aes aes = new AesManaged(); aes.Key = passwordDeriveBytes.GetBytes(aes.KeySize / 8); aes.IV = passwordDeriveBytes.GetBytes(aes.BlockSize / 8); CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write); cryptoStream.Write(DataToDecrypt, 0, DataToDecrypt.Length); cryptoStream.Close(); return memoryStream.ToArray(); } }

This is the class where we have our DBContext


using System;using System.Collections.Generic;using System.ComponentModel.DataAnnotations;using System.Data.Entity;using System.Data.Entity.ModelConfiguration.Conventions;using System.Linq;using System.Text;using System.Threading.Tasks; public class TokenCacheDataContext : DbContext { public TokenCacheDataContext() : base("TokenCacheDataContext") { } public DbSet<PerUserWebCache> PerUserCacheList { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); } }


Initializer…

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; public class TokenCacheInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<TokenCacheDataContext> { }

Well, I hope you enjoy, any questions let me know.

Thursday, 19 February 2015

Creating Your Own Helper for the Azure AD Graph API

If you want to implement Azure AD in an Enterprise Solution, you will probably looking for of adding extensions (attributes) to users, so you can add things like Company Name or Twitter Account. I agree you could save that into a nice table in a Database, but why do you need to do that if you can use Azure AD as a database to store all these details.

This technique will eliminate dependencies and will make the application more scalable. At the end of the day, Azure AD is a black box we can’t “invade” but we can can manipulate.

In this post you will see few things which you will need in order to make the whole Azure AD something useful, so things like:

  • Adding/Removing Extensions.
  • Override the claims so we can extract the extensions.

The first thing we are going to do it is to Create an out-of-the-box MVC application which will include our UX to perform the actions. Below there we will create our helper with the actions and models.

So let’s go to start… Add a new MVC project, and click OK. Then click on Change Authentication:
image 

Select Organizational Accounts and in the Domain box enter your tenant, it could be somekindofcompany.onmicrosoft.com (If you don’t have a domain you will need to create one on Azure, this is a nice post if you don’t have a clue). Select “Sign on read and write …” at the end of the day you are going to be manipulating the Azure AD tenant, so you need permissions. You the will be promtend to login, just login with the admin account, and go for it!, click ok.
 image

Now! if you run the application you will be prompted with the login screen, just login and you will see the default page. At this stage, if you manage to login, you will realize that you have a nice little token to perform RESTFul operations with you Application. This is what you should get after doing the Login.
image

Ok! So far so good…

Let’s go to add a project in the solution. This project will be the one in charge of keeping the Models for our Application and doing the Restful operations. It will not care about the tenant, clientid etc… we will be passing that. So Ok, Add a new project to the solution and call it AADGraphAPIManager, when you finish remove Class1.cs. This is how our solution should look like:
image

Now we are going to create two folders in the AADGraphAPIManager project, one it will be called Models and the other called Services.
Under Models we are going to add a class called AADGraphApiModel.cs
image

Inside this class let’s go to copy the model from the Graph API version 1.5, I assume that in few months time we will have a new version. Okp, don’t you worry apparently Microsoft is going to be supporting all the versions for a while. (the code is below)

 

using Newtonsoft.Json;
using System.Collections.Generic;
using System.ComponentModel;

namespace AADGraphAPIManager.Models
{
public class UserContext
{
[JsonProperty(
"odata.metadata")]
public string userContext;

[JsonProperty(
"value")]
public List<UserDetails> value;
}

public class UserDetails
{
[JsonProperty(
"objectId")]
public string objectId { get; set; }

[DisplayName(
"Display Name")]
public string displayName { get; set; }
[DisplayName(
"Given Name")]
public string givenName { get; set; }
[DisplayName(
"Surname")]
public string surname { get; set; }
[DisplayName(
"Job Title")]
public string jobTitle { get; set; }
[DisplayName(
"Department")]
public string department { get; set; }
[DisplayName(
"Mobile")]
public string mobile { get; set; }
[DisplayName(
"City")]
public string city { get; set; }
[DisplayName(
"Street Address")]
public string streetAddress { get; set; }
[DisplayName(
"Country")]
public string country { get; set; }
[DisplayName(
"Postal Code")]
public string postalCode { get; set; }
[DisplayName(
"Phone Number")]
public string telephoneNumber { get; set; }
[DisplayName(
"Email Address")]
public string mail { get; set; }
[DisplayName(
"UPN")]
public string userPrincipalName { get; set; }
[DisplayName(
"Last DirSync")]
public string lastDirSyncTime { get; set; }

//## THIS ONES ARE NEW ONES ... SO EXTENSIONS
[DisplayName("Client Id")]
public string ClientId { get; set; }
[DisplayName(
"Client Name")]
public string ClientName { get; set; }
[DisplayName(
"Client Address")]
public string ClientAddress { get; set; }
[DisplayName(
"Client Post Code")]
public string ClientPostCode { get; set; }
[DisplayName(
"Client Country")]
public string ClientCountry { get; set; }
}

public class AppContext
{
[JsonProperty(
"odata.metadata")]
public string metadata { get; set; }
public List<AppDetails> value { get; set; }
}

public class AppDetails
{
public string objectId { get; set; }
public string appId { get; set; }
}

public class ExtensionPropertiesContext
{
[JsonProperty(
"odata.metadata")]
public string metadata { get; set; }
public List<ExtensionProperty> value { get; set; }
}

public class ExtensionProperty
{
public string objectId { get; set; }
public string objectType { get; set; }
public string name { get; set; }
public string dataType { get; set; }
[JsonProperty(
"odata.metadata")]
public string odataMetadata { get; set; }
[JsonProperty(
"odata.type")]
public string odataType { get; set; }
public List<string> targetObjects { get; set; }
}
}


As you probably noticed, JsonProperty is highlighted, that is because we need to a add Newtonsoft Json Package to recognise the attributes. You can use the windows ones, but you get use to Newtonsoft I suspect. Ok in order to add the Nuget package go to your package console, select this project and type:
Install-Package Newtonsoft.Json
It should be something like this:
image 


Let’s go to add under our folder “Services” a new class called GraphAPIQuery.cs and add the code below.
You will need to install the following Nuget packages under your project


Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory


 



using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using AADGraphAPIManager.Authentication;
using AADGraphAPIManager.Authentication.ExtensionMethods;
using AADGraphAPIManager.Models;
using System.Net;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace AADGraphAPIManager.Services
{
public class GraphAPIQuery : IGraphAPIQuery
{
private string _graphUrl; //## https://graph.windows.net
private string _apiVersion; //## 1.5
private string _tenantIdClaimType; //## http://schemas.microsoft.com/identity/claims/tenantid
private string _LoginUrl; //## https://login.windows.net/{0}
private string _appPrincipalId; //## ConfigurationManager.AppSettings["ida:ClientID"];
private string _appKey; //## ConfigurationManager.AppSettings["ida:Password"];
private string _tenantId;
private string _appObjectId;

private ClaimsPrincipal _claim;

private AzureADAuthentication _aadAuthentication;

#region [ Enums ]
public enum ExtensionTarget {
User,
Group,
TenantDetail,
Application,
ServicePrincipal
}
#endregion

#region [ Properties ]
public bool IsGraphApiHasBeenInitializedProperly { get; set; }
public string ApiVersion
{
get { return _apiVersion; }
set { _apiVersion = value; }
}
public ClaimsPrincipal Claim
{
get { return _claim; }
}
#endregion

#region [ Constructor ]
public GraphAPIQuery(string AppPrincipalId, string AppKey, ClaimsPrincipal claim)
{
this.IsGraphApiHasBeenInitializedProperly = false;

try
{
//## DEFAULT Microsoft Azure AD Values
_graphUrl = @"https://graph.windows.net";
_tenantIdClaimType
= @"http://schemas.microsoft.com/identity/claims/tenantid";
_apiVersion
= "api-version=1.5";

//## Other vars
_claim = claim;
_tenantId
= _claim.FindFirst(_tenantIdClaimType).Value;
_LoginUrl
= String.Format(CultureInfo.InvariantCulture, "https://login.windows.net/{0}", _tenantId);
_appPrincipalId
= AppPrincipalId;
_appKey
= AppKey;

//## INITIALIZING AADAUTH
_aadAuthentication = new AzureADAuthentication();

//## GET AUTHENTICATION RESULT
_aadAuthentication.GetAuthenticationResult(_graphUrl, _tenantId, _LoginUrl, _appPrincipalId, _appKey);

//## GETTING THE APPLICATION OBJECT ID
_appObjectId = this.GetAppObjectId();

this.IsGraphApiHasBeenInitializedProperly = true; ;
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
}
}
#endregion

#region [ Methods ]

#region [ Public Methods ]
public UserDetails GetUserByObjectId(string userId)
{
try
{

// check if token is expired or about to expire in 2 minutes
if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this._aadAuthentication.AadAuthenticationResult = this._aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId + "/users/" + userId + "?" + this._apiVersion;

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();

request.Headers.Add(authnHeader);
request.Method
= "GET";
using (var response = request.GetResponse())
{
using (var stream = response.GetResponseStream())
{
StreamReader reader
= new StreamReader(stream);
string responseString = reader.ReadToEnd();
UserDetails getUser
= JsonConvert.DeserializeObject<UserDetails>(responseString);

return getUser;
}
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}
}

public UserDetails GetUserByName(string name)
{
try
{
// check if token is expired or about to expire in 2 minutes
if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this._aadAuthentication.AadAuthenticationResult = this._aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId + "/users/" + name + "?" + this._apiVersion;

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();

request.Headers.Add(authnHeader);
request.Method
= "GET";
using (var response = request.GetResponse())
{
using (var stream = response.GetResponseStream())
{
StreamReader reader
= new StreamReader(stream);
string responseString = reader.ReadToEnd();
UserDetails getUser
= JsonConvert.DeserializeObject<UserDetails>(responseString);

return getUser;
}
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}
}

public AadGroups GetGroups()
{
// check if token is expired or about to expire in 2 minutes
if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this._aadAuthentication.AadAuthenticationResult = this._aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

if (this._aadAuthentication.AadAuthenticationResult == null)
return null;

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId +"/groups" + "?" + this.ApiVersion;

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();

request.Headers.Add(authnHeader);
request.Method
= "GET";

using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception(String.Format(
"Server error (HTTP {0}: {1}).",
response.StatusCode,
response.StatusDescription));
else
using (var stream = response.GetResponseStream())
{
StreamReader reader
= new StreamReader(stream);
string responseString = reader.ReadToEnd();
AadGroups groupList
= JsonConvert.DeserializeObject<AadGroups>(responseString);

return groupList;
}
}
}

catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}
}

public IList<object> GetUserGroups(string name)
{
//AadGroups groups = null;
IList<object> groups = new List<object>();
try
{
// check if token is expired or about to expire in 2 minutes
if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this._aadAuthentication.AadAuthenticationResult = this._aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId + "/users/" + name + "/memberOf" +"?" + this._apiVersion;

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();

request.Headers.Add(authnHeader);
request.Method
= "GET";
using (var response = request.GetResponse())
{
using (var stream = response.GetResponseStream())
{
StreamReader reader
= new StreamReader(stream);
string responseString = reader.ReadToEnd();
dynamic _groups
= JsonConvert.DeserializeObject<dynamic>(responseString);
foreach (object item in _groups.value)
{
groups.Add(item);
}
Newtonsoft.Json.Linq.JObject groups1
= Newtonsoft.Json.Linq.JObject.Parse(responseString);

}
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}

return groups;
}

private ExtensionRepresentation CreateExtension(ExtensionRepresentation extension)
{
if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this.aadAuthentication.AadAuthenticationResult = this.aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

if (this._aadAuthentication.AadAuthenticationResult == null)
return null;

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId + "/applications/" + this._appObjectId + "/extensionProperties" + "?" + this.ApiVersion;

JsonSerializerSettings jsonSettings
= new JsonSerializerSettings();
jsonSettings.NullValueHandling
= NullValueHandling.Ignore;
jsonSettings.DefaultValueHandling
= DefaultValueHandling.Ignore;
JsonSerializer serializer
= JsonSerializer.CreateDefault(jsonSettings);
string body = JsonConvert.SerializeObject(JObject.FromObject(extension, serializer), Formatting.None, jsonSettings);

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();

request.Headers.Add(authnHeader);
request.Method
= "POST";
request.ContentType
= "application/json";
byte[] data = encoding.GetBytes(body);
request.ContentLength
= data.Length;
using (Stream stream = request.GetRequestStream())
{
stream.Write(data,
0, data.Length);
}

using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.Created)
{
throw new Exception(String.Format(
"Server error (HTTP {0}: {1}).",
response.StatusCode,
response.StatusDescription));
}
else
using (var stream = response.GetResponseStream())
{
string payload;
using (System.IO.StreamReader reader = new System.IO.StreamReader(stream))
{
payload
= reader.ReadToEnd();
}

return JObject.Parse(payload).ToObject<ExtensionRepresentation>();
}
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}
}

public AadGroup CreateNewGroup(string groupName)
{
AadGroup groupObject;

GroupCreationModel group
= new GroupCreationModel();
group.displayName
= groupName;
group.mailNickname
= groupName;
group.mailEnabled
="false";
group.securityEnabled
= "true";


if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this.aadAuthentication.AadAuthenticationResult = this.aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

if (this._aadAuthentication.AadAuthenticationResult == null)
return null;

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId + "/groups" + "?" + this.ApiVersion;

JsonSerializerSettings jsonSettings
= new JsonSerializerSettings();
jsonSettings.NullValueHandling
= NullValueHandling.Ignore;
jsonSettings.DefaultValueHandling
= DefaultValueHandling.Ignore;
JsonSerializer serializer
= JsonSerializer.CreateDefault(jsonSettings);
string body = JsonConvert.SerializeObject(JObject.FromObject(group, serializer), Formatting.None, jsonSettings);

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();

request.Headers.Add(authnHeader);
request.Method
= "POST";
request.ContentType
= "application/json";
byte[] data = encoding.GetBytes(body);
request.ContentLength
= data.Length;
using (Stream stream = request.GetRequestStream())
{
stream.Write(data,
0, data.Length);
}

using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.Created)
{
throw new Exception(String.Format(
"Server error (HTTP {0}: {1}).",
response.StatusCode,
response.StatusDescription));
}
else
using (var stream = response.GetResponseStream())
{
string payload;
using (System.IO.StreamReader reader = new System.IO.StreamReader(stream))
{
payload
= reader.ReadToEnd();
groupObject
= JsonConvert.DeserializeObject<AadGroup>(payload);
}
}
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}

return groupObject;
}

public bool RegisterNewExtension(string strExtension, ExtensionTarget extensionTarget)
{
string target = "";

if (CheckExtensionRegistered(strExtension) == null)
{
// setup the extension definition
ExtensionRepresentation extension = new ExtensionRepresentation();
extension.name
= strExtension;
extension.dataType
= "String";
switch (extensionTarget)
{
case ExtensionTarget.User: target = "User"; break;
case ExtensionTarget.Group: target = "Group"; break;
case ExtensionTarget.TenantDetail: target = "TenantDetail"; break;
case ExtensionTarget.Application: target = "Application"; break;
case ExtensionTarget.ServicePrincipal: target = "ServicePrincipal"; break;
default: target = "User"; break;
}
extension.targetObjects.Add(target);

// Execute the POST to create new extension
ExtensionRepresentation returnedExtension = this.CreateExtension(extension);
return returnedExtension != null;
}
else
return true;
}

public bool UnRegisterExtension(string strExtension)
{
string extensionObjectId = this.CheckExtensionRegisteredAndReturningExtensionObjectId(strExtension);

if (extensionObjectId != null)
{
return this.RemoveExtensionRegistered(extensionObjectId);
}
else
return true;
}

public bool RegisterClientExtensionsInGroups()
{
bool result = false;

if (RegisterNewExtension("ClientId", AADGraphAPIManager.Services.GraphAPIQuery.ExtensionTarget.Group))
{
if (RegisterNewExtension("ClientName", AADGraphAPIManager.Services.GraphAPIQuery.ExtensionTarget.Group))
{
if (RegisterNewExtension("ClientAddress", AADGraphAPIManager.Services.GraphAPIQuery.ExtensionTarget.Group))
{
if (RegisterNewExtension("ClientPostCode", AADGraphAPIManager.Services.GraphAPIQuery.ExtensionTarget.Group))
{
if (RegisterNewExtension("ClientCountry", AADGraphAPIManager.Services.GraphAPIQuery.ExtensionTarget.Group))
{
result
= true;
}
}
}
}
}

return result;
}
#endregion

#region [ Private Methods ]
public string CheckExtensionRegistered(string extensionName)
{
string responseString = string.Empty;
// check if token is expired or about to expire in 2 minutes
if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this._aadAuthentication.AadAuthenticationResult = this._aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId + "/applications/" + this._appObjectId + "/extensionProperties" + "?" + this._apiVersion;

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();

request.Headers.Add(authnHeader);
request.Method
= "GET";
using (var response = request.GetResponse())
{
using (var stream = response.GetResponseStream())
{
StreamReader reader
= new StreamReader(stream);
responseString
= reader.ReadToEnd();
}
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}

//##
var extensionproperties = JsonConvert.DeserializeObject<ExtensionPropertiesContext>(responseString);

if (extensionproperties.value.Count == 0)
return null;
else
{
var extensions
= extensionproperties.value;
for (int i = 0; i < extensionproperties.value.Count; i++)
{
if (extensionproperties.value[i].name.Contains(extensionName))
return extensionproperties.value[i].name;
}
}

return null;
}

private string CheckExtensionRegisteredAndReturningExtensionObjectId(string extensionName)
{
string responseString = string.Empty;
// check if token is expired or about to expire in 2 minutes
if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this._aadAuthentication.AadAuthenticationResult = this._aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId + "/applications/" + this._appObjectId + "/extensionProperties" + "?" + this._apiVersion;

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();

request.Headers.Add(authnHeader);
request.Method
= "GET";
using (var response = request.GetResponse())
{
using (var stream = response.GetResponseStream())
{
StreamReader reader
= new StreamReader(stream);
responseString
= reader.ReadToEnd();
}
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}

//##
var extensionproperties = JsonConvert.DeserializeObject<ExtensionPropertiesContext>(responseString);

if (extensionproperties.value.Count == 0)
return null;
else
{
var extensions
= extensionproperties.value;
for (int i = 0; i < extensionproperties.value.Count; i++)
{
if (extensionproperties.value[i].name.Contains(extensionName))
return extensionproperties.value[i].objectId;
}
}

return null;
}

private bool RemoveExtensionRegistered(string extensionObjectId)
{
string responseString = string.Empty;
// check if token is expired or about to expire in 2 minutes
if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this._aadAuthentication.AadAuthenticationResult = this._aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId + "/applications/" + this._appObjectId + "/extensionProperties/" + extensionObjectId + "?" + this._apiVersion;

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();
request.Headers.Add(authnHeader);
request.Method
= "DELETE";
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.NoContent)
{
throw new Exception(String.Format(
"Server error (HTTP {0}: {1}).",
response.StatusCode,
response.StatusDescription));

return false;
}
else
{
return true;
}
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return false;
}

//##
}

public bool SetExtensionValue(string entityName, string extensionName, string extensionValue, ExtensionTarget extensionTarget)
{
string target = string.Empty;

switch (extensionTarget)
{
case ExtensionTarget.User: target = "users"; break;
case ExtensionTarget.Group: target = "groups"; break;
case ExtensionTarget.TenantDetail: target = "tenantdetails"; break;
case ExtensionTarget.Application: target = "applications"; break;
case ExtensionTarget.ServicePrincipal: target = "servicePrincipals"; break;
default: target = "users"; break;
}
////Get the objectId for this particular app
//string requestUrl = String.Format(
// CultureInfo.InvariantCulture,
// GraphExtensionValueUrl,
// HttpUtility.UrlEncode(tenantId),
// HttpUtility.UrlEncode(upn));

//HttpClient client = new HttpClient();
//client.DefaultRequestHeaders.ExpectContinue = false;
////PATCH isn't a default method
//HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("PATCH"), requestUrl);
//request.Headers.TryAddWithoutValidation("Authorization", authHeader);

//string extensionProperty = "{\"" + extensionName + "\":\"" + extensionValue + "\"}";

//request.Content = new StringContent(extensionProperty, System.Text.Encoding.UTF8, "application/json");

//HttpResponseMessage response = await client.SendAsync(request);
//string responseString = await response.Content.ReadAsStringAsync();
//if (response.StatusCode == HttpStatusCode.NoContent)
//{
// return true;
//}
//else
// return false;

//##
if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this._aadAuthentication.AadAuthenticationResult = this._aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId + "/" + target + "/" + entityName + "" + "?" + this._apiVersion;

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();

string body = "{\"" + extensionName + "\":\"" + extensionValue + "\"}";

request.Headers.Add(authnHeader);
request.Method
= "PATCH";
request.ContentType
= "application/json";
byte[] data = encoding.GetBytes(body);
request.ContentLength
= data.Length;
using (Stream stream = request.GetRequestStream())
{
stream.Write(data,
0, data.Length);
}

using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.NoContent)
return true;
else
return false;
}
}

catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return false;
}
}

public bool CheckIfGroupExists(string groupName)
{
string responseString = string.Empty;
AadGroups groupList;
// check if token is expired or about to expire in 2 minutes
if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this._aadAuthentication.AadAuthenticationResult = this._aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId + "/groups?$filter=displayName eq '" + groupName+"'"+ "&" + this._apiVersion;

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();

request.Headers.Add(authnHeader);
request.Method
= "GET";
using (var response = request.GetResponse())
{
using (var stream = response.GetResponseStream())
{
StreamReader reader
= new StreamReader(stream);
responseString
= reader.ReadToEnd();
groupList
= JsonConvert.DeserializeObject<AadGroups>(responseString);
}
}
}
catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return false;
}

return groupList.group.Count>0?true:false;
}

private string GetAppObjectId()
{
string appObjectId = string.Empty;

// check if token is expired or about to expire in 2 minutes
if (this._aadAuthentication.AadAuthenticationResult.IsExpired() || this._aadAuthentication.AadAuthenticationResult.WillExpireIn(2))
{
//this._aadAuthentication.AadAuthenticationResult = this._aadAuthentication.GetNewAuthenticationResult(ref strErrors);
}

if (this._aadAuthentication.AadAuthenticationResult == null)
return null;

string authnHeader = "Authorization: " + this._aadAuthentication.AadAuthenticationResult.AccessToken;

string uri = this._graphUrl + "/" + this._tenantId + "/applications" + "?" + this.ApiVersion;

try
{
HttpWebRequest request
= (HttpWebRequest)WebRequest.Create(uri);
System.Text.ASCIIEncoding encoding
= new System.Text.ASCIIEncoding();

request.Headers.Add(authnHeader);
request.Method
= "GET";

using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception(String.Format(
"Server error (HTTP {0}: {1}).",
response.StatusCode,
response.StatusDescription));
else
using (var stream = response.GetResponseStream())
{
StreamReader reader
= new StreamReader(stream);
string responseString = reader.ReadToEnd();
AppContext appContext
= JsonConvert.DeserializeObject<AppContext>(responseString);

//Iterate through the list to find the correct application,
//and retrieve it's object id
for (int i = 0; i < appContext.value.Count; i++)
{
if (appContext.value[i].appId == this._appPrincipalId)
{
appObjectId
= appContext.value[i].objectId;

}
}
}
}
}

catch (WebException ex)
{
System.Diagnostics.Trace.TraceError(ex.ToString());
return null;
}


return appObjectId;
}

#endregion

#endregion

}
}


 


Let’s go to add under our folder “Services” a new interface called IGraphAPIQuery.cs and add the code below.


using AADGraphAPIManager.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AADGraphAPIManager.Services
{
public interface IGraphAPIQuery
{
UserDetails GetUserByObjectId(
string userId);
UserDetails GetUserByName(
string name);
AadGroups GetGroups();
IList
<object> GetUserGroups(string name);
AadGroup CreateNewGroup(
string groupName);
bool RegisterNewExtension(string strExtension, AADGraphAPIManager.Services.GraphAPIQuery.ExtensionTarget extensionTarget);
bool UnRegisterExtension(string strExtension);
bool RegisterClientExtensionsInGroups();
string CheckExtensionRegistered(string extensionName);
bool SetExtensionValue(string entityName, string extensionName, string extensionValue, AADGraphAPIManager.Services.GraphAPIQuery.ExtensionTarget extensionTarget);
bool CheckIfGroupExists(string groupName);
}
}

All these operations are the basic ones, we will extend the code doing a wrapper around the GraphAPIQuery. I am not going to explain all the process, but you have to be focus in adding extensions. To add an extension to a group or user you need to create the group or the user first. That basically means you have to do a double call. Unfortunately there is no way around you.


Another issue comes when you go to Azure AD to edit the groups or users, don’t expect to find the extensions them on the Azure portal, so you need to create a User Interface for that with CRUD operations via Web.


I am going to post the code, so you can have a look, but remember this is a beta version Smile , there is plenty of stuff it needs to be cleaned. At least you will be able to see how you can create extensions for Groups and Users, so you can extend it for your own purposes. I have mixed different ways to do the calls as well as serialization, so you can grab your favourites.


To make this work, you need to instantiate UserOperations and pass the appPrincipalId (which is the ClientID of the Azure Application Instance (ie: string like this "8353c878-c925-4567-b900-0985b0805e6a")), the appKey (which is the unique key of the Azure Application Instance (ie: string like this "GJu+cHCkinvK9HvSY60LDH7347x4CgMvXJz2udiEzes=")) and the ClaimsPrincipal (you just need to pass ClaimPrincipal.Current).


UserOperations useroperations = new UserOperations("8353c878-c925-4567-b900-0985b0805e6a","GJu+cHCkinvK9HvSY60LDH7347x4CgMvXJz2udiEzes=",ClaimsPrincipal.Current);

Download Code Here