diff --git a/plugins/arcgis/service/src/ArcGISConfig.ts b/plugins/arcgis/service/src/ArcGISConfig.ts index be99ed10b..82b69a769 100644 --- a/plugins/arcgis/service/src/ArcGISConfig.ts +++ b/plugins/arcgis/service/src/ArcGISConfig.ts @@ -85,12 +85,22 @@ export interface ArcGISAuthConfig { /** * The username for authentication. */ - username: string + username?: string /** * The password for authentication. */ - password: string + password?: string + + /** + * The Client Id for OAuth + */ + clientId?: string + + /** + * The Client secret for OAuth + */ + clientSecret?: string } /** diff --git a/plugins/arcgis/service/src/HttpClient.ts b/plugins/arcgis/service/src/HttpClient.ts index a68841f80..5c4497dd8 100644 --- a/plugins/arcgis/service/src/HttpClient.ts +++ b/plugins/arcgis/service/src/HttpClient.ts @@ -121,11 +121,14 @@ export class HttpClient { * Sends a get request to the specified url. * @param url The url of the get request. */ - sendGet(url: string) { + async sendGet(url: string): Promise { const console = this._console + let response; this.sendGetHandleResponse(url, function (chunk) { console.log('Response: ' + chunk); + response = chunk; }) + return response; } /** diff --git a/plugins/arcgis/service/src/index.ts b/plugins/arcgis/service/src/index.ts index a008ee345..66902d6cb 100644 --- a/plugins/arcgis/service/src/index.ts +++ b/plugins/arcgis/service/src/index.ts @@ -12,7 +12,6 @@ import { FeatureServiceResult } from './FeatureServiceResult' import { ArcGISIdentityManager } from "@esri/arcgis-rest-request" // import { IQueryFeaturesOptions, queryFeatures } from '@esri/arcgis-rest-feature-service' - const logPrefix = '[mage.arcgis]' const logMethods = ['log', 'debug', 'info', 'warn', 'error'] as const const consoleOverrides = logMethods.reduce((overrides, fn) => { @@ -65,25 +64,44 @@ function getServerUrl(featureServiceUrl: string): string { * * @throws {Error} If the identity manager could not be created due to missing required query parameters. */ -async function handleAuthentication(req: express.Request): Promise { +async function handleAuthentication(req: express.Request, httpClient: HttpClient): Promise { const featureUsername = req.query.username as string | undefined; const featurePassword = req.query.password as string | undefined; + const featureClientId = req.query.clientId as string | undefined; + const featureClientSecret = req.query.clientSecret as string | undefined; const featureServer = req.query.server as string | undefined; const featurePortal = req.query.portal as string | undefined; const featureToken = req.query.token as string | undefined; + const portalUrl = getPortalUrl(req.query.featureUrl as string ?? ''); let identityManager: ArcGISIdentityManager; try { if (featureToken) { console.log('Token provided for authentication'); - identityManager = await ArcGISIdentityManager.fromToken({ token: featureToken, server: getServerUrl(req.query.featureUrl as string ?? ''), portal: getPortalUrl(req.query.featureUrl as string ?? '') }); + identityManager = await ArcGISIdentityManager.fromToken({ token: featureToken, server: getServerUrl(req.query.featureUrl as string ?? ''), portal: portalUrl }); } else if (featureUsername && featurePassword) { console.log('Username and password provided for authentication, username:' + featureUsername); identityManager = await ArcGISIdentityManager.signIn({ username: featureUsername, password: featurePassword, - portal: getPortalUrl(req.query.featureUrl as string ?? ''), + portal: portalUrl, + }); + } else if (featureClientId && featureClientSecret) { + console.log('ClientId and Client secret provided for authentication'); + const params = { + client_id: featureClientId, + client_secret: featureClientSecret, + grant_type: 'client_credentials', + expiration: 900 + } + + const url = `${portalUrl}/oauth2/token?client_id=${params.client_id}&client_secret=${params.client_secret}&grant_type=${params.grant_type}&expiration=${params.expiration}` + const response = await httpClient.sendGet(url); + identityManager = await ArcGISIdentityManager.fromToken({ + clientId: featureClientId, + token: JSON.parse(response)?.access_token || '', + portal: portalUrl }); } else { throw new Error('Missing required query parameters to authenticate (token or username/password).'); @@ -118,21 +136,15 @@ const arcgisPluginHooks: InitPluginHook = { // - Move getPortalUrl to Helper file // - Update layer token to get token from identity manager // - Move plugins/arcgis/web-app/projects/main/src/lib/arc-layer/arc-layer.component.ts addLayer to helper file and use instead of encodeURIComponent + // - Remove Client secret from returned Config object if applicable const processor = new ObservationProcessor(stateRepo, eventRepo, obsRepoForEvent, userRepo, console); processor.start(); return { webRoutes: { public: (requestContext: GetAppRequestContext) => { - const routes = express.Router().use(express.json()) - routes.post('/oauth/signin', async (req, res, next) => { - // TODO implement - }) - - routes.post('/oauth/authenticate', async (req, res, next) => { - // TODO implement - }) - + const routes = express.Router().use(express.json()); + // TODO: Add User initiated Oauth return routes }, protected: (requestContext: GetAppRequestContext) => { @@ -165,15 +177,15 @@ const arcgisPluginHooks: InitPluginHook = { const featureUrl = req.query.featureUrl as string; console.info('Getting ArcGIS layer info for ' + featureUrl) let identityManager: ArcGISIdentityManager; + const httpClient = new HttpClient(console); - try { - identityManager = await handleAuthentication(req); + try { + identityManager = await handleAuthentication(req, httpClient); - const featureUrlAndToken = featureUrl + '?token=' + encodeURIComponent(identityManager.token); - console.log('featureUrlAndToken', featureUrlAndToken); - const httpClient = new HttpClient(console); - - httpClient.sendGetHandleResponse(featureUrlAndToken, (chunk) => { + const featureUrlAndToken = featureUrl + '?token=' + encodeURIComponent(identityManager.token); + console.log('featureUrlAndToken', featureUrlAndToken); + + httpClient.sendGetHandleResponse(featureUrlAndToken, (chunk) => { console.info('ArcGIS layer info response ' + chunk); try { const featureServiceResult = JSON.parse(chunk) as FeatureServiceResult; @@ -188,8 +200,8 @@ const arcgisPluginHooks: InitPluginHook = { } }); } catch (err) { - res.status(500).json({ message: 'Could not get ArcGIS layer info', error: err }); - } + res.status(500).json({ message: 'Could not get ArcGIS layer info', error: err }); + } }) return routes diff --git a/plugins/arcgis/web-app/projects/main/src/lib/ArcGISConfig.ts b/plugins/arcgis/web-app/projects/main/src/lib/ArcGISConfig.ts index be99ed10b..15194f081 100644 --- a/plugins/arcgis/web-app/projects/main/src/lib/ArcGISConfig.ts +++ b/plugins/arcgis/web-app/projects/main/src/lib/ArcGISConfig.ts @@ -11,7 +11,7 @@ export interface FeatureServiceConfig { /** * Access token */ - token?: string + token?: string // TODO?: Perhaps move to the auth property? /** * Username and password for ArcGIS authentication @@ -82,15 +82,27 @@ export interface FeatureLayerConfig { */ export interface ArcGISAuthConfig { + // TODO?: May want to add authType property + /** * The username for authentication. */ - username: string + username?: string /** * The password for authentication. */ - password: string + password?: string + + /** + * The Client Id for OAuth + */ + clientId?: string + + /** + * The Client secret for OAuth + */ + clientSecret?: string } /** diff --git a/plugins/arcgis/web-app/projects/main/src/lib/arc-layer/arc-layer.component.html b/plugins/arcgis/web-app/projects/main/src/lib/arc-layer/arc-layer.component.html index df469804c..c937d7e3e 100644 --- a/plugins/arcgis/web-app/projects/main/src/lib/arc-layer/arc-layer.component.html +++ b/plugins/arcgis/web-app/projects/main/src/lib/arc-layer/arc-layer.component.html @@ -8,6 +8,10 @@

Feature Layers

+
+ +
There are no ArcGIS feature services currently being synchronized. @@ -70,7 +74,7 @@

Layers

+ (click)="onAddLayerUrl({ layerUrl: layerUrl.value, selectableLayers: layers, layerToken: layerToken.value })">SAVE diff --git a/plugins/arcgis/web-app/projects/main/src/lib/arc-layer/arc-layer.component.ts b/plugins/arcgis/web-app/projects/main/src/lib/arc-layer/arc-layer.component.ts index 3d5b0d588..f523458f9 100644 --- a/plugins/arcgis/web-app/projects/main/src/lib/arc-layer/arc-layer.component.ts +++ b/plugins/arcgis/web-app/projects/main/src/lib/arc-layer/arc-layer.component.ts @@ -24,7 +24,7 @@ export class ArcLayerComponent implements OnInit { isLoading: boolean; currentUrl?: string; private timeoutId: number; - + @ViewChild('addLayerDialog', { static: true }) private addLayerTemplate: TemplateRef @ViewChild('deleteLayerDialog', { static: true }) @@ -123,6 +123,20 @@ export class ArcLayerComponent implements OnInit { }) } + onSignIn() { + this.arcService.authenticate().subscribe({ + next(x) { + console.log('got value ' + JSON.stringify(x)); + }, + error(err) { + console.error('something wrong occurred: ' + err); + }, + complete() { + console.log('done'); + }, + }); + } + onAddLayer() { this.currentUrl = undefined this.arcLayerControl.setValue('') @@ -151,12 +165,6 @@ export class ArcLayerComponent implements OnInit { this.arcService.putArcConfig(this.config); } - - // Define the overloads - onAddLayerUrl(layerUrl: string, layerToken: string, layers: ArcLayerSelectable[]): void; - onAddLayerUrl(layerUrl: string, username: string, password: string, layers: ArcLayerSelectable[]): void; - - // Implement the function /** * Adds a new layer to the configuration if it does not already exist. * @@ -174,8 +182,17 @@ export class ArcLayerComponent implements OnInit { * 6. Updates the configuration and emits the change. * 7. Persists the updated configuration using `arcService`. */ - onAddLayerUrl(layerUrl: string, arg2: string, arg3: string | ArcLayerSelectable[], arg4?: ArcLayerSelectable[]): void { + onAddLayerUrl(params: { + layerUrl: string, + selectableLayers: ArcLayerSelectable[], + layerToken?: string, + username?: string, + password?: string, + clientId?: string, + clientSecret?: string + }): void { let serviceConfigToEdit = null; + const { layerUrl, selectableLayers, layerToken, username, password, clientId, clientSecret } = params; // Search if the layer in config to edit for (const service of this.config.featureServices) { @@ -183,34 +200,30 @@ export class ArcLayerComponent implements OnInit { serviceConfigToEdit = service; } } - // Determine if layers in 3rd or 4th argument - const layers = typeof arg3 === 'string' ? arg4 : arg3; // Add layer if it doesn't exist if (serviceConfigToEdit == null) { console.log('Adding layer ' + layerUrl); - let token: string | null = null; const featureLayer: FeatureServiceConfig = { url: layerUrl, token: undefined, - auth: { - username: '', - password: '' - }, + auth: {}, layers: [] } as FeatureServiceConfig; - if (typeof arg3 === 'string') { + if (username) { // Handle username and password case - featureLayer.auth = { username: arg2, password: arg3 }; - } else { + featureLayer.auth = { username, password }; + } else if (clientId) { + featureLayer.auth = { clientId, clientSecret }; + }else { // Handle token case - featureLayer.token = arg2; + featureLayer.token = layerToken; } - if (layers) { - for (const aLayer of layers) { + if (selectableLayers) { + for (const aLayer of selectableLayers) { if (aLayer.isSelected) { const layerConfig = { layer: aLayer.name, @@ -229,8 +242,8 @@ export class ArcLayerComponent implements OnInit { } else { // Edit existing layer console.log('Saving edited layer ' + layerUrl) const editedLayers = []; - if (layers) { - for (const aLayer of layers) { + if (selectableLayers) { + for (const aLayer of selectableLayers) { if (aLayer.isSelected) { let layerConfig = null if (serviceConfigToEdit.layers != null) { diff --git a/plugins/arcgis/web-app/projects/main/src/lib/arc.service.ts b/plugins/arcgis/web-app/projects/main/src/lib/arc.service.ts index 7f1b1a41b..f940c2738 100644 --- a/plugins/arcgis/web-app/projects/main/src/lib/arc.service.ts +++ b/plugins/arcgis/web-app/projects/main/src/lib/arc.service.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http' import { Injectable } from '@angular/core' -import { Observable } from 'rxjs' +import { Observable, Subject } from 'rxjs' import { ArcGISPluginConfig } from './ArcGISPluginConfig' import { FeatureServiceResult } from './FeatureServiceResult' import { EventResult } from './EventsResult' @@ -30,6 +30,29 @@ export class ArcService { return this.http.get(`${baseUrl}/arcgisLayers?featureUrl=${featureUrl}`) } + authenticate(): Observable { + let subject = new Subject(); + + const url = `${baseUrl}/oauth/sign-in`; + const authWindow = window.open(url, "_blank"); + + function onMessage(event: any) { + window.removeEventListener('message', onMessage, false); + + if (event.origin !== window.location.origin) { + return; + } + + subject.next(event.data) + + // authWindow?.close(); + } + + authWindow?.addEventListener('message', onMessage, false); + + return subject.asObservable() + } + fetchEvents() { return this.http.get(`${apiBaseUrl}/events?populate=false&projection={"name":true,"id":true}`) }