Skip to content

Commit f7cb430

Browse files
committed
[web/service] API update to include feature service authentication status
1 parent 46cdaf4 commit f7cb430

15 files changed

+228
-282
lines changed

plugins/arcgis/service/src/ArcGISConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface FeatureServiceConfig {
1212
* Serialized ArcGISIdentityManager
1313
*/
1414
identityManager: string
15-
15+
1616
/**
1717
* The feature layers.
1818
*/

plugins/arcgis/service/src/ArcGISService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { FeatureServiceConfig } from './ArcGISConfig'
33
import { PluginStateRepository } from '@ngageoint/mage.service/lib/plugins.api'
44

55
export interface ArcGISIdentityService {
6-
getIdentityManager(featureService: FeatureServiceConfig): Promise<ArcGISIdentityManager>
6+
signin(featureService: FeatureServiceConfig): Promise<ArcGISIdentityManager>
77
updateIndentityManagers(): Promise<void>
88
}
99

@@ -13,7 +13,7 @@ export function createArcGISIdentityService(
1313
const identityManagerCache: Map<string, Promise<ArcGISIdentityManager>> = new Map()
1414

1515
return {
16-
async getIdentityManager(featureService: FeatureServiceConfig): Promise<ArcGISIdentityManager> {
16+
async signin(featureService: FeatureServiceConfig): Promise<ArcGISIdentityManager> {
1717
let cached = await identityManagerCache.get(featureService.url)
1818
if (!cached) {
1919
const identityManager = ArcGISIdentityManager.deserialize(featureService.identityManager)

plugins/arcgis/service/src/FeatureServiceAdmin.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ObservationsTransformer } from "./ObservationsTransformer"
77
import { LayerInfoResult, LayerField } from "./LayerInfoResult"
88
import FormData from 'form-data'
99
import { request } from '@esri/arcgis-rest-request'
10-
import { ArcGISIdentityService, getFeatureServiceUrl } from "./ArcGISService"
10+
import { ArcGISIdentityService } from "./ArcGISService"
1111

1212
/**
1313
* Administers hosted feature services such as layer creation and updates.
@@ -405,7 +405,7 @@ export class FeatureServiceAdmin {
405405
const form = new FormData()
406406
form.append('addToDefinition', JSON.stringify(layer))
407407

408-
const identityManager = await this._identityService.getIdentityManager(service)
408+
const identityManager = await this._identityService.signin(service)
409409
const postResponse = await request(url, {
410410
authentication: identityManager,
411411
httpMethod: 'POST',
@@ -427,12 +427,12 @@ export class FeatureServiceAdmin {
427427

428428
this._console.info('ArcGIS feature layer addToDefinition (add fields) url ' + url)
429429

430-
const identityManager = await this._identityService.getIdentityManager(service)
430+
const identityManager = await this._identityService.signin(service)
431431
await request(url, {
432432
authentication: identityManager,
433433
params: {
434-
addToDefinition: layer,
435-
f: "json"
434+
addToDefinition: layer,
435+
f: "json"
436436
}
437437
}).then((postResponse) => {
438438
console.log('Response: ' + postResponse)
@@ -462,7 +462,7 @@ export class FeatureServiceAdmin {
462462

463463
this._console.info('ArcGIS feature layer deleteFromDefinition (delete fields) url ' + url)
464464

465-
const identityManager = await this._identityService.getIdentityManager(service)
465+
const identityManager = await this._identityService.signin(service)
466466
const postResponse = request(url, {
467467
authentication: identityManager,
468468
httpMethod: 'POST',

plugins/arcgis/service/src/ObservationProcessor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ export class ObservationProcessor {
200200
private async getFeatureServiceLayers(config: ArcGISPluginConfig) {
201201
for (const service of config.featureServices) {
202202
try {
203-
const identityManager = await this._identityService.getIdentityManager(service)
203+
const identityManager = await this._identityService.signin(service)
204204
const response = await request(service.url, { authentication: identityManager })
205205
this.handleFeatureService(response, service, config)
206206
} catch (err) {
@@ -261,7 +261,7 @@ export class ObservationProcessor {
261261

262262
if (layerId != null) {
263263
featureLayer.layer = layerId
264-
const identityManager = await this._identityService.getIdentityManager(featureServiceConfig)
264+
const identityManager = await this._identityService.signin(featureServiceConfig)
265265
const featureService = new FeatureService(console, featureServiceConfig, identityManager)
266266
const layerInfo = await featureService.queryLayerInfo(layerId);
267267
const url = `${featureServiceConfig.url}/${layerId}`;
@@ -285,7 +285,7 @@ export class ObservationProcessor {
285285
const admin = new FeatureServiceAdmin(config, this._identityService, this._console)
286286
await admin.updateLayer(featureServiceConfig, featureLayer, layerInfo, this._eventRepo)
287287
const info = new LayerInfo(url, events, layerInfo)
288-
const identityManager = await this._identityService.getIdentityManager(featureServiceConfig)
288+
const identityManager = await this._identityService.signin(featureServiceConfig)
289289
const layerProcessor = new FeatureLayerProcessor(info, config, identityManager, this._console);
290290
this._layerProcessors.push(layerProcessor);
291291
// clearTimeout(this._nextTimeout); // TODO why is this needed?

plugins/arcgis/service/src/index.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { ArcGISIdentityManager, request } from "@esri/arcgis-rest-request"
99
import { FeatureServiceConfig } from './ArcGISConfig'
1010
import { URL } from "node:url"
1111
import express from 'express'
12-
import { createArcGISIdentityService, getPortalUrl } from './ArcGISService'
12+
import { ArcGISIdentityService, createArcGISIdentityService, getPortalUrl } from './ArcGISService'
1313

1414
const logPrefix = '[mage.arcgis]'
1515
const logMethods = ['log', 'debug', 'info', 'warn', 'error'] as const
@@ -35,9 +35,15 @@ const InjectedServices = {
3535

3636
const pluginWebRoute = "plugins/@ngageoint/mage.arcgis.service"
3737

38-
const sanitizeFeatureService = (config: FeatureServiceConfig): Omit<FeatureServiceConfig, 'identityManager'> => {
38+
const sanitizeFeatureService = async (config: FeatureServiceConfig, identityService: ArcGISIdentityService): Promise<Omit<FeatureServiceConfig & { authenticated: boolean }, 'identityManager'>> => {
39+
let authenticated = false
40+
try {
41+
await identityService.signin(config)
42+
authenticated = true
43+
} catch(ignore) {}
44+
3945
const { identityManager, ...sanitized } = config;
40-
return sanitized;
46+
return { ...sanitized, authenticated }
4147
}
4248

4349
/**
@@ -117,7 +123,7 @@ const arcgisPluginHooks: InitPluginHook<typeof InjectedServices> = {
117123
config.featureServices.push(service)
118124

119125
await processor.putConfig(config)
120-
const sanitizedService = sanitizeFeatureService(service)
126+
const sanitizedService = await sanitizeFeatureService(service, identityService)
121127
res.send(`
122128
<html>
123129
<head>
@@ -148,8 +154,13 @@ const arcgisPluginHooks: InitPluginHook<typeof InjectedServices> = {
148154
.get(async (req, res, next) => {
149155
console.info('Getting ArcGIS plugin config...')
150156
const config = await processor.safeGetConfig()
151-
const { featureServices, ...remaining } = config
152-
res.json({ ...remaining, featureServices: featureServices.map((service) => sanitizeFeatureService(service)) })
157+
const { featureServices, ...remaining } = config
158+
159+
const sanitizeFeatureServices = await Promise.all(
160+
featureServices.map(async (service) => await sanitizeFeatureService(service, identityService))
161+
)
162+
163+
res.json({ ...remaining, featureServices: sanitizeFeatureServices })
153164
})
154165
.put(async (req, res, next) => {
155166
console.info('Applying ArcGIS plugin config...')
@@ -209,7 +220,7 @@ const arcgisPluginHooks: InitPluginHook<typeof InjectedServices> = {
209220
}
210221

211222
await processor.patchConfig(config)
212-
return res.send(sanitizeFeatureService(service))
223+
return res.send(sanitizeFeatureService(service, identityService))
213224
} catch (err) {
214225
return res.send('Invalid credentials provided to communicate with feature service').status(400)
215226
}
@@ -224,7 +235,7 @@ const arcgisPluginHooks: InitPluginHook<typeof InjectedServices> = {
224235
}
225236

226237
try {
227-
const identityManager = await identityService.getIdentityManager(featureService)
238+
const identityManager = await identityService.signin(featureService)
228239
const response = await request(url, {
229240
authentication: identityManager
230241
})

plugins/arcgis/web-app/projects/main/src/lib/ArcGISConfig.ts

Lines changed: 2 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,14 @@ export interface FeatureServiceConfig {
99
url: string
1010

1111
/**
12-
* Username and password for ArcGIS authentication
12+
* Flag indicating valid authentication
1313
*/
14-
auth?: ArcGISAuthConfig
15-
16-
/**
17-
* Create layers that don't exist
18-
*/
19-
createLayers?: boolean
20-
21-
/**
22-
* The administration url to the arc feature service.
23-
*/
24-
adminUrl?: string
25-
26-
/**
27-
* Administration access token
28-
*/
29-
adminToken?: string
14+
authenticated: boolean
3015

3116
/**
3217
* The feature layers.
3318
*/
3419
layers: FeatureLayerConfig[]
35-
3620
}
3721

3822
/**
@@ -50,92 +34,12 @@ export interface FeatureLayerConfig {
5034
*/
5135
geometryType?: string
5236

53-
/**
54-
* Access token
55-
*/
56-
token?: string // TODO - can this be removed? Will Layers have a token too?
5737
/**
5838
* The event ids or names that sync to this arc feature layer.
5939
*/
6040
events?: (number|string)[]
61-
62-
/**
63-
* Add layer fields from form fields
64-
*/
65-
addFields?: boolean
66-
67-
/**
68-
* Delete editable layer fields missing from form fields
69-
*/
70-
deleteFields?: boolean
71-
7241
}
7342

74-
export enum AuthType {
75-
Token = 'token',
76-
UsernamePassword = 'usernamePassword',
77-
OAuth = 'oauth'
78-
}
79-
80-
81-
/**
82-
* Contains token-based authentication configuration.
83-
*/
84-
export interface TokenAuthConfig {
85-
type: AuthType.Token
86-
token: string
87-
authTokenExpires?: string
88-
}
89-
90-
/**
91-
* Contains username and password for ArcGIS server authentication.
92-
*/
93-
export interface UsernamePasswordAuthConfig {
94-
type: AuthType.UsernamePassword
95-
/**
96-
* The username for authentication.
97-
*/
98-
username: string
99-
100-
/**
101-
* The password for authentication.
102-
*/
103-
password: string
104-
}
105-
106-
/**
107-
* Contains OAuth authentication configuration.
108-
*/
109-
export interface OAuthAuthConfig {
110-
type: AuthType.OAuth
111-
/**
112-
* The Client Id for OAuth
113-
*/
114-
clientId: string
115-
/**
116-
* The redirectUri for OAuth
117-
*/
118-
redirectUri?: string
119-
120-
/**
121-
* The temporary auth token for OAuth
122-
*/
123-
authToken?: string
124-
125-
/**
126-
* The expiration date for the temporary token
127-
*/
128-
authTokenExpires?: string
129-
}
130-
131-
/**
132-
* Union type for authentication configurations.
133-
*/
134-
export type ArcGISAuthConfig =
135-
| TokenAuthConfig
136-
| UsernamePasswordAuthConfig
137-
| OAuthAuthConfig
138-
13943
/**
14044
* Attribute configurations
14145
*/

plugins/arcgis/web-app/projects/main/src/lib/arc-layer/arc-layer-dialog.component.html

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,47 @@
11
<div class="dialog">
2-
<div mat-dialog-title>ArcGIS Feature Service</div>
2+
<div class="cancel">
3+
<button mat-icon-button (click)="onCancel()">
4+
<mat-icon>close</mat-icon>
5+
</button>
6+
</div>
37

8+
<div mat-dialog-title>ArcGIS Feature Service</div>
49
<mat-dialog-content>
510
<div class="dialog-content">
611
<mat-accordion>
7-
<mat-expansion-panel [expanded]="state === State.Validate" (opened)="state = State.Validate">
12+
<mat-expansion-panel [expanded]="state === State.Validate" (opened)="state = State.Validate" [disabled]="featureService?.authenticated">
813
<mat-expansion-panel-header>
9-
<mat-panel-title>Feature Service</mat-panel-title>
14+
<mat-panel-title *ngIf="!featureService?.authenticated">Feature Service</mat-panel-title>
15+
<mat-panel-title *ngIf="featureService?.authenticated">{{featureService?.url}}</mat-panel-title>
16+
<mat-panel-description *ngIf="featureService && !featureService?.authenticated">
17+
<div class="invalid-credentials">
18+
<mat-icon>error_outline</mat-icon> Credentials invalid or expired
19+
</div>
20+
</mat-panel-description>
1021
</mat-expansion-panel-header>
1122

1223
<form class="validate" [formGroup]="layerForm" class="form">
1324
<mat-form-field>
1425
<mat-label>URL</mat-label>
15-
<input matInput formControlName="url" required
16-
placeholder="http{s}://{domain}/arcgis/rest/services/{service}/FeatureServer" />
26+
<input matInput formControlName="url" required placeholder="http{s}://{domain}/arcgis/rest/services/{service}/FeatureServer"/>
1727
<mat-error *ngIf="layerForm.hasError('required')">URL is required</mat-error>
1828
</mat-form-field>
1929

2030
<mat-form-field appearance="fill">
2131
<mat-label>Authentication</mat-label>
2232
<mat-select formControlName="authenticationType">
23-
<mat-option *ngFor="let authenticationType of authenticationTypes" [value]="authenticationType.value">
24-
{{authenticationType.title}}
33+
<mat-option *ngFor="let authenticationState of authenticationStates" [value]="authenticationState.value">
34+
{{authenticationState.text}}
2535
</mat-option>
2636
</mat-select>
2737
</mat-form-field>
2838

2939
<ng-container [ngSwitch]="layerForm.controls.authenticationType.value">
3040
<div *ngSwitchCase="AuthenticationType.Token" formGroupName="token">
3141
<mat-form-field appearance="fill">
32-
<mat-label>Token</mat-label>
42+
<mat-label>API Key</mat-label>
3343
<input matInput formControlName="token" required />
34-
<mat-error>Token is required</mat-error>
44+
<mat-error>API Key is required</mat-error>
3545
</mat-form-field>
3646
</div>
3747
<div *ngSwitchCase="AuthenticationType.OAuth" formGroupName="oauth">
@@ -61,7 +71,8 @@
6171
<button mat-flat-button color="primary" [disabled]="loading" (click)="onValidate()">
6272
<div class="actions__save">
6373
<mat-spinner *ngIf="loading" diameter="16"></mat-spinner>
64-
<span>Validate</span>
74+
<span *ngIf="!featureService">Create Feature Server</span>
75+
<span *ngIf="featureService">Update Feature Server</span>
6576
</div>
6677
</button>
6778
</div>
@@ -85,7 +96,7 @@
8596
<button mat-flat-button color="primary" [disabled]="loading" (click)="onSave()">
8697
<div class="actions__save">
8798
<mat-spinner *ngIf="loading" diameter="16"></mat-spinner>
88-
<span>Save</span>
99+
<span>Set Layers</span>
89100
</div>
90101
</button>
91102
</div>

plugins/arcgis/web-app/projects/main/src/lib/arc-layer/arc-layer-dialog.component.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ mat-dialog-content {
66
flex: 1
77
}
88

9+
.cancel {
10+
position: absolute;
11+
top: -16px;
12+
right: -16px;
13+
}
14+
915
.dialog {
1016
min-width: 700px;
1117
min-height: 450px;
@@ -24,6 +30,13 @@ mat-dialog-content {
2430
margin-bottom: 16px;
2531
}
2632

33+
.invalid-credentials {
34+
color: #F44336;
35+
display: flex;
36+
align-items: center;
37+
gap: 8px;
38+
}
39+
2740
.actions {
2841
width: 100%;
2942
display: flex;

0 commit comments

Comments
 (0)