Skip to content

Commit

Permalink
Authentication types abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
Rick Saccoccia committed Oct 3, 2024
1 parent 9d5ee44 commit f65a5e3
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 84 deletions.
49 changes: 36 additions & 13 deletions plugins/arcgis/service/src/ArcGISConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ export interface FeatureServiceConfig {
*/
url: string

/**
* Access token
*/
token?: string

/**
* Username and password for ArcGIS authentication
*/
Expand Down Expand Up @@ -58,8 +53,7 @@ export interface FeatureLayerConfig {
/**
* Access token
*/
token?: string

token?: string // TODO - can this be removed? Will Layers have a token too?
/**
* The event ids or names that sync to this arc feature layer.
*/
Expand All @@ -77,26 +71,47 @@ export interface FeatureLayerConfig {

}

export enum AuthType {
Token = 'token',
UsernamePassword = 'usernamePassword',
OAuth = 'oauth'
}


/**
* Contains username and password for ArcGIS server authentication.
* Contains token-based authentication configuration.
*/
export interface ArcGISAuthConfig {
export interface TokenAuthConfig {
type: AuthType.Token
token: string
authTokenExpires?: string
}

/**
* Contains username and password for ArcGIS server authentication.
*/
export interface UsernamePasswordAuthConfig {
type: AuthType.UsernamePassword
/**
* The username for authentication.
*/
username?: string
username: string

/**
* The password for authentication.
*/
password?: string
password: string
}

/**
* Contains OAuth authentication configuration.
*/
export interface OAuthAuthConfig {
type: AuthType.OAuth
/**
* The Client Id for OAuth
*/
clientId?: string

clientId: string
/**
* The redirectUri for OAuth
*/
Expand All @@ -123,6 +138,14 @@ export interface ArcGISAuthConfig {
refreshTokenExpires?: string
}

/**
* Union type for authentication configurations.
*/
export type ArcGISAuthConfig =
| TokenAuthConfig
| UsernamePasswordAuthConfig
| OAuthAuthConfig

/**
* Attribute configurations
*/
Expand Down
2 changes: 1 addition & 1 deletion plugins/arcgis/service/src/FeatureServiceAdmin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ export class FeatureServiceAdmin {
private httpClient(service: FeatureServiceConfig): HttpClient {
let token = service.adminToken
if (token == null) {
token = service.token
token = service.auth?.type == 'token' ? service.auth.token : ""
}
return new HttpClient(console, token)
}
Expand Down
10 changes: 5 additions & 5 deletions plugins/arcgis/service/src/ObservationProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { EventTransform } from './EventTransform';
import { GeometryChangedHandler } from './GeometryChangedHandler';
import { EventDeletionHandler } from './EventDeletionHandler';
import { EventLayerProcessorOrganizer } from './EventLayerProcessorOrganizer';
import { FeatureServiceConfig, FeatureLayerConfig } from "./ArcGISConfig"
import { FeatureServiceConfig, FeatureLayerConfig, AuthType } from "./ArcGISConfig"
import { PluginStateRepository } from '@ngageoint/mage.service/lib/plugins.api'
import { FeatureServiceAdmin } from './FeatureServiceAdmin';

Expand Down Expand Up @@ -173,12 +173,12 @@ export class ObservationProcessor {
* @param config The plugins configuration.
*/
private getFeatureServiceLayers(config: ArcGISPluginConfig) {

// TODO: What is the impact of what this is doing? Do we need to account for usernamePassword auth type services?
for (const service of config.featureServices) {

const services: FeatureServiceConfig[] = []

if (service.token == null) {
if (service.auth?.type !== AuthType.Token || service.auth?.token == null) {
const tokenServices = new Map()
const nonTokenLayers = []
for (const layer of service.layers) {
Expand All @@ -204,7 +204,7 @@ export class ObservationProcessor {
}

for (const serv of services) {
const featureService = new FeatureService(console, serv.token)
const featureService = new FeatureService(console, (serv.auth?.type === AuthType.Token && serv.auth?.token != null) ? serv.auth.token : '')
featureService.queryFeatureService(serv.url, (featureServiceResult: FeatureServiceResult) => this.handleFeatureService(featureServiceResult, serv, config))
}
}
Expand Down Expand Up @@ -233,7 +233,7 @@ export class ObservationProcessor {
for (const featureLayer of featureServiceConfig.layers) {

if (featureLayer.token == null) {
featureLayer.token = featureServiceConfig.token
featureLayer.token = featureServiceConfig.auth?.type == AuthType.Token ? featureServiceConfig.auth.token : ""
}

const eventNames: string[] = []
Expand Down
22 changes: 13 additions & 9 deletions plugins/arcgis/service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { UserRepositoryToken } from '@ngageoint/mage.service/lib/plugins.api/plu
import { SettingPermission } from '@ngageoint/mage.service/lib/entities/authorization/entities.permissions'
import express from 'express'
import { ArcGISPluginConfig } from './ArcGISPluginConfig'
import { OAuthAuthConfig, AuthType } from './ArcGISConfig'
import { ObservationProcessor } from './ObservationProcessor'
import { HttpClient } from './HttpClient'
import { FeatureServiceResult } from './FeatureServiceResult'
Expand Down Expand Up @@ -103,9 +104,9 @@ async function handleAuthentication(req: express.Request, httpClient: HttpClient
// Check if feature service has refresh token and use that to generate token to use
// Else complain
const config = await processor.safeGetConfig();
const featureService = config.featureServices.find((service) => service.auth?.clientId === featureClientId);
const authToken = featureService?.auth?.authToken;
const authTokenExpires = featureService?.auth?.authTokenExpires as string;
const featureService = config.featureServices.find((service) => service.auth?.type === AuthType.OAuth);
const authToken = (featureService?.auth as OAuthAuthConfig)?.authToken;
const authTokenExpires = (featureService?.auth as OAuthAuthConfig)?.authTokenExpires as string;
if (authToken && new Date(authTokenExpires) > new Date()) {
// TODO: error handling
identityManager = await ArcGISIdentityManager.fromToken({
Expand All @@ -114,8 +115,8 @@ async function handleAuthentication(req: express.Request, httpClient: HttpClient
portal: portalUrl
});
} else {
const refreshToken = featureService?.auth?.refreshToken;
const refreshTokenExpires = featureService?.auth?.refreshTokenExpires as string;
const refreshToken = (featureService?.auth as OAuthAuthConfig)?.refreshToken;
const refreshTokenExpires = (featureService?.auth as OAuthAuthConfig)?.refreshTokenExpires as string;
if (refreshToken && new Date(refreshTokenExpires) > new Date()) {
const url = `${portalUrl}/oauth2/token?client_id=${featureClientId}&refresh_token=${refreshToken}&grant_type=refresh_token`
const response = await httpClient.sendGet(url)
Expand All @@ -131,7 +132,7 @@ async function handleAuthentication(req: express.Request, httpClient: HttpClient
}
}
} else {
throw new Error('Missing required query parameters to authenticate (token or username/password).');
throw new Error('Missing required query parameters to authenticate (token or username/password or oauth parameters).');
}

console.log('Identity Manager token', identityManager.token);
Expand Down Expand Up @@ -181,6 +182,7 @@ const arcgisPluginHooks: InitPluginHook<typeof InjectedServices> = {
url: portal,
layers: [],
auth: {
type: AuthType.OAuth,
clientId: clientId,
redirectUri: redirectUri
}
Expand All @@ -199,8 +201,8 @@ const arcgisPluginHooks: InitPluginHook<typeof InjectedServices> = {
const config = await processor.safeGetConfig();
const featureService = config.featureServices[0];
const creds = {
clientId: featureService.auth?.clientId as string,
redirectUri: featureService.auth?.redirectUri as string,
clientId: (featureService.auth as OAuthAuthConfig)?.clientId as string,
redirectUri: (featureService.auth as OAuthAuthConfig)?.redirectUri as string,
portal: featureService.url as string
}
ArcGISIdentityManager.exchangeAuthorizationCode(creds, code)
Expand All @@ -210,7 +212,9 @@ const arcgisPluginHooks: InitPluginHook<typeof InjectedServices> = {
authToken: idManager.token,
authTokenExpires: idManager.tokenExpires.toISOString(),
refreshToken: idManager.refreshToken,
refreshTokenExpires: idManager.refreshTokenExpires.toISOString()
refreshTokenExpires: idManager.refreshTokenExpires.toISOString(),
type: AuthType.OAuth,
clientId: creds.clientId
}
await processor.putConfig(config);
res.status(200).json({})
Expand Down
73 changes: 57 additions & 16 deletions plugins/arcgis/web-app/projects/main/src/lib/ArcGISConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ export interface FeatureServiceConfig {
*/
url: string

/**
* Access token
*/
token?: string // TODO?: Perhaps move to the auth property?

/**
* Username and password for ArcGIS authentication
*/
Expand Down Expand Up @@ -58,8 +53,7 @@ export interface FeatureLayerConfig {
/**
* Access token
*/
token?: string

token?: string // TODO - can this be removed? Will Layers have a token too?
/**
* The event ids or names that sync to this arc feature layer.
*/
Expand All @@ -77,34 +71,81 @@ export interface FeatureLayerConfig {

}

export enum AuthType {
Token = 'token',
UsernamePassword = 'usernamePassword',
OAuth = 'oauth'
}


/**
* Contains username and password for ArcGIS server authentication.
* Contains token-based authentication configuration.
*/
export interface ArcGISAuthConfig {

// TODO?: May want to add authType property
export interface TokenAuthConfig {
type: AuthType.Token
token: string
authTokenExpires?: string
}

/**
* Contains username and password for ArcGIS server authentication.
*/
export interface UsernamePasswordAuthConfig {
type: AuthType.UsernamePassword
/**
* The username for authentication.
*/
username?: string
username: string

/**
* The password for authentication.
*/
password?: string
password: string
}

/**
* Contains OAuth authentication configuration.
*/
export interface OAuthAuthConfig {
type: AuthType.OAuth
/**
* The Client Id for OAuth
*/
clientId?: string
clientId: string
/**
* The redirectUri for OAuth
*/
redirectUri?: string

/**
* The temporary auth token for OAuth
*/
authToken?: string

/**
* The expiration date for the temporary token
*/
authTokenExpires?: string

/**
* The Refresh token for OAuth
*/
refreshToken?: string

/**
* The Client secret for OAuth
* The expiration date for the Refresh token
*/
clientSecret?: string
refreshTokenExpires?: string
}

/**
* Union type for authentication configurations.
*/
export type ArcGISAuthConfig =
| TokenAuthConfig
| UsernamePasswordAuthConfig
| OAuthAuthConfig

/**
* Attribute configurations
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ <h3 matDialogTitle>Layers</h3>
<mat-dialog-actions align="end">
<button mat-button matDialogClose>CANCEL</button>
<button [disabled]="isSaveDisabled()" mat-flat-button color="primary" matDialogClose
(click)="onAddLayerUrl({ layerUrl: layerUrl.value, selectableLayers: layers, layerToken: layerToken.value })">SAVE</button>
(click)="onAddLayerUrl({ layerUrl: layerUrl.value, selectableLayers: layers, authType: AuthType.Token, layerToken: layerToken.value })">SAVE</button>
</mat-dialog-actions>
</ng-template>
<ng-template #deleteLayerDialog let-data>
Expand Down
Loading

0 comments on commit f65a5e3

Please sign in to comment.