Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication types abstraction #220

Merged
merged 1 commit into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading