Skip to content

Commit

Permalink
Added basic support for OpenID Connect. (#756)
Browse files Browse the repository at this point in the history
  • Loading branch information
azaslonov authored Jul 16, 2020
1 parent 83ed448 commit a610022
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export class OperationDetails {

if (this.authorizationServers) {
const associatedAuthServer = this.authorizationServers
.find(x => x.id === api.authenticationSettings?.oAuth2?.authorizationServerId);
.find(x => x.name === api.authenticationSettings?.oAuth2?.authorizationServerId);

this.associatedAuthServer(associatedAuthServer);
}
Expand Down
41 changes: 41 additions & 0 deletions src/contracts/openIdConnectMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export interface OpenIdConnectMetadata {
/**
* e.g. "https://sts.windows.net/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".
*/
issuer: string;

/**
* e.g. "https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/oauth2/v2.0/authorize".
*/
authorization_endpoint: string;

/**
* e.g. "https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/oauth2/token".
*/
token_endpoint: string;

/**
* e.g. "openid".
*/
scopes_supported: string[];

/**
* e.g. "code", "id_token", "code id_token", "token id_token", "token".
*/
response_types_supported: string[];

/**
* e.g. "query", "fragment", "form_post".
*/
response_modes_supported: string[];

/**
* e.g. "authorization_code", "implicit".
*/
grant_types_supported: string[];

/**
* e.g. "client_secret_post", "private_key_jwt", "client_secret_basic".
*/
token_endpoint_auth_methods_supported: string[];
}
35 changes: 35 additions & 0 deletions src/contracts/openIdConnectProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ArmResource } from "./armResource";

/**
* OpenId Connect Provider details.
*/
export interface OpenIdConnectProviderContract extends ArmResource {
properties: OpenIdConnectProviderProperties;
}

export interface OpenIdConnectProviderProperties {
/**
* User-friendly OpenID Connect Provider name.
*/
displayName: string;

/**
* User-friendly description of OpenID Connect Provider.
*/
description: string;

/**
* Metadata endpoint URI.
*/
metadataEndpoint: string;

/**
* Client ID of developer console which is the client application.
*/
clientId: string;

/**
* Client Secret of developer console which is the client application.
*/
clientSecret: string;
}
23 changes: 14 additions & 9 deletions src/models/authorizationServer.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { GrantTypes } from "./../constants";
import { AuthorizationServerContract } from "./../contracts/authorizationServer";

export class AuthorizationServer {
public readonly id: string;
public readonly displayName: string;
public readonly clientId: string;
public readonly authorizationEndpoint: string;
public readonly tokenEndpoint: string;
public readonly grantTypes: string[];
public name: string;
public displayName: string;
public description: string;
public clientId: string;
public authorizationEndpoint: string;
public tokenEndpoint: string;
public grantTypes: string[];
public scopes: string[];


constructor(contract: AuthorizationServerContract) {
this.id = contract.name;
constructor(contract?: AuthorizationServerContract) {
if (!contract) {
return;
}

this.name = contract.name;
this.displayName = contract.properties.displayName;
this.description = contract.properties.description;
this.clientId = contract.properties.clientId;
this.authorizationEndpoint = contract.properties.authorizationEndpoint;
this.tokenEndpoint = contract.properties.tokenEndpoint;
Expand Down
37 changes: 37 additions & 0 deletions src/models/openIdConnectProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { OpenIdConnectProviderContract } from "../contracts/openIdConnectProvider";

export class OpenIdConnectProvider {
constructor(contract: OpenIdConnectProviderContract) {
this.name = contract.name;
this.displayName = contract.properties.displayName;
this.description = contract.properties.description;
this.clientId = contract.properties.clientId;
this.metadataEndpoint = contract.properties.metadataEndpoint;
}

/**
* Resource name.
*/
public name: string;

/**
* User-friendly OpenID Connect Provider name.
*/
public displayName: string;

/**
* User-friendly description of OpenID Connect Provider.
*/
public description: string;

/**
* Metadata endpoint URI.
*/
public metadataEndpoint: string;

/**
* Client ID of developer console which is the client application.
*/

public clientId: string;
}
62 changes: 56 additions & 6 deletions src/services/oauthService.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,50 @@
import { OpenIdConnectMetadata } from "./../contracts/openIdConnectMetadata";

import * as ClientOAuth2 from "client-oauth2";
import { HttpClient } from "@paperbits/common/http";
import { GrantTypes } from "./../constants";
import { MapiClient } from "./mapiClient";
import { AuthorizationServerContract } from "../contracts/authorizationServer";
import { AuthorizationServer } from "../models/authorizationServer";
import { PageContract } from "../contracts/page";
import { OpenIdConnectProviderContract } from "../contracts/openIdConnectProvider";
import { OpenIdConnectProvider } from "./../models/openIdConnectProvider";

export class OAuthService {
constructor(private readonly mapiClient: MapiClient) { }
constructor(
private readonly mapiClient: MapiClient,
private readonly httpClient: HttpClient
) { }

public async getOAuthServers(): Promise<AuthorizationServer[]> {
public async getOpenIdConnectProviders(): Promise<OpenIdConnectProvider[]> {
try {
const pageOfAuthservers = await this.mapiClient.get<PageContract<AuthorizationServerContract>>("/authorizationServers");
const pageOfAuthservers = await this.mapiClient.get<PageContract<OpenIdConnectProviderContract>>("/openidConnectProviders");
return pageOfAuthservers.value.map(authServer => new OpenIdConnectProvider(authServer));
}
catch (error) {
throw new Error(`Unable to fetch configured authorization servers.`);
}
}

return pageOfAuthservers.value.map(authServer => new AuthorizationServer(authServer));
public async getOAuthServers(): Promise<AuthorizationServer[]> {
try {
const authorizationServers = [];
const pageOfOAuthServers = await this.mapiClient.get<PageContract<AuthorizationServerContract>>("/authorizationServers");
const oauthServers = pageOfOAuthServers.value.map(authServer => new AuthorizationServer(authServer));
authorizationServers.push(...oauthServers);

const pageOfOicdServers = await this.mapiClient.get<PageContract<OpenIdConnectProviderContract>>("/openidConnectProviders");
const oicdServers = pageOfOicdServers.value.map(authServer => new OpenIdConnectProvider(authServer));

for (const provider of oicdServers) {
const authServer = await this.discoverOAuthServer(provider.metadataEndpoint);
authServer.clientId = provider.clientId;
authServer.displayName = provider.displayName;
authServer.description = provider.description;
authorizationServers.push(authServer);
}

return authorizationServers;
}
catch (error) {
throw new Error(`Unable to fetch configured authorization servers.`);
Expand Down Expand Up @@ -72,7 +104,7 @@ export class OAuthService {
}

public async authenticateCode(authorizationServer: AuthorizationServer): Promise<string> {
const redirectUri = `https://${location.hostname}/signin-oauth/code/callback/${authorizationServer.id}`;
const redirectUri = `https://${location.hostname}/signin-oauth/code/callback/${authorizationServer.name}`;

const oauthClient = new ClientOAuth2({
clientId: authorizationServer.clientId,
Expand Down Expand Up @@ -100,7 +132,7 @@ export class OAuthService {
}

public async authenticateClientCredentials(authorizationServer: AuthorizationServer): Promise<string> {
const uri = `https://${location.hostname}/signin-oauth/credentials/${authorizationServer.id}`;
const uri = `https://${location.hostname}/signin-oauth/credentials/${authorizationServer.name}`;

return new Promise<string>((resolve, reject) => {
window.open(uri, "_blank", "width=400,height=500");
Expand All @@ -118,4 +150,22 @@ export class OAuthService {
window.addEventListener("message", receiveMessage, false);
});
}

public async discoverOAuthServer(metadataEndpoint: string): Promise<AuthorizationServer> {
const response = await this.httpClient.send<OpenIdConnectMetadata>({ url: metadataEndpoint });
const metadata = response.toObject();

const server = new AuthorizationServer();
server.authorizationEndpoint = metadata.authorization_endpoint;
server.tokenEndpoint = metadata.token_endpoint;
server.scopes = metadata.scopes_supported || ["openid"];

const supportedGrantTypes = Object.values(GrantTypes).map(x => x.toString());

server.grantTypes = metadata.grant_types_supported
? metadata.grant_types_supported.filter(grantType => supportedGrantTypes.includes(grantType))
: [GrantTypes.authorizationCode];

return server;
}
}

0 comments on commit a610022

Please sign in to comment.