[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[reclaim-ui] 172/459: manual merge from https://gitlab.com/voggenre/ui d
From: |
gnunet |
Subject: |
[reclaim-ui] 172/459: manual merge from https://gitlab.com/voggenre/ui due to refactoring |
Date: |
Fri, 11 Jun 2021 23:24:24 +0200 |
This is an automated email from the git hooks/post-receive script.
martin-schanzenbach pushed a commit to branch master
in repository reclaim-ui.
commit 08ad046c1125fb469eecb896d13dc6f06fedb372
Author: Schanzenbach, Martin <mschanzenbach@posteo.de>
AuthorDate: Fri Jan 17 16:39:38 2020 +0100
manual merge from https://gitlab.com/voggenre/ui due to refactoring
---
src/app/app-routing.module.ts | 4 +
src/app/app.module.ts | 6 +-
src/app/attestation.ts | 6 +
src/app/attribute.ts | 8 +-
.../authorization-request.component.html | 10 +-
.../authorization-request.component.ts | 4 +
.../edit-attestations.component.css | 0
.../edit-attestations.component.html | 83 ++++
.../edit-attestations.component.ts | 182 +++++++++
.../edit-authorizations.component.css | 0
.../edit-authorizations.component.html | 64 +++
.../edit-authorizations.component.ts | 144 +++++++
src/app/edit-identity/edit-identity.component.html | 168 ++++++--
src/app/edit-identity/edit-identity.component.ts | 443 +++++++++++++++++----
src/app/identity-list/identity-list.component.html | 21 +-
src/app/identity-list/identity-list.component.ts | 114 +++++-
src/app/open-id.service.ts | 31 +-
src/app/reclaim.service.ts | 46 ++-
src/app/reference.ts | 6 +
19 files changed, 1203 insertions(+), 137 deletions(-)
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 51b645f..bbe1986 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -3,6 +3,8 @@ import { RouterModule, Routes } from '@angular/router';
import { IdentityListComponent } from
'./identity-list/identity-list.component';
import { NewIdentityComponent } from './new-identity/new-identity.component';
import { EditIdentityComponent } from
'./edit-identity/edit-identity.component';
+import { EditAuthorizationsComponent } from
'./edit-authorizations/edit-authorizations.component';
+import { EditAttestationsComponent } from
'./edit-attestations/edit-attestations.component';
import { AuthorizationRequestComponent } from
'./authorization-request/authorization-request.component';
const routes: Routes = [
@@ -11,6 +13,8 @@ const routes: Routes = [
{ path: '', redirectTo: '/index.html', pathMatch: 'full' },
{ path: 'new-identity', component: NewIdentityComponent },
{ path: 'edit-identity/:id', component: EditIdentityComponent },
+ { path: 'edit-authorizations/:id', component: EditAuthorizationsComponent
},
+ { path: 'edit-attestations/:id', component: EditAttestationsComponent },
{ path: 'authorization-request', component: AuthorizationRequestComponent }
];
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index d1c8b7a..13fe6db 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -18,6 +18,8 @@ import { OpenIdService } from './open-id.service';
import { NewIdentityComponent } from './new-identity/new-identity.component';
import { EditIdentityComponent } from
'./edit-identity/edit-identity.component';
import { AuthorizationRequestComponent } from
'./authorization-request/authorization-request.component';
+import { EditAuthorizationsComponent } from
'./edit-authorizations/edit-authorizations.component';
+import { EditAttestationsComponent } from
'./edit-attestations/edit-attestations.component';
@NgModule({
declarations: [
@@ -27,7 +29,9 @@ import { AuthorizationRequestComponent } from
'./authorization-request/authoriza
SearchPipe,
NewIdentityComponent,
EditIdentityComponent,
- AuthorizationRequestComponent
+ AuthorizationRequestComponent,
+ EditAuthorizationsComponent,
+ EditAttestationsComponent
],
imports: [
BrowserModule,
diff --git a/src/app/attestation.ts b/src/app/attestation.ts
new file mode 100644
index 0000000..a71d114
--- /dev/null
+++ b/src/app/attestation.ts
@@ -0,0 +1,6 @@
+export class Attestation {
+ constructor(public name: string,
+ public id: string,
+ public value: string,
+ public type: string) {}
+}
diff --git a/src/app/attribute.ts b/src/app/attribute.ts
index ccd7f46..f720887 100644
--- a/src/app/attribute.ts
+++ b/src/app/attribute.ts
@@ -1,5 +1,7 @@
export class Attribute {
- constructor(public name: string, public id: string, public value: string,
- public type: string) {
- }
+ constructor(public name: string,
+ public id: string,
+ public value: string,
+ public type: string,
+ public flag: string) {}
}
diff --git a/src/app/authorization-request/authorization-request.component.html
b/src/app/authorization-request/authorization-request.component.html
index 421b066..44fd346 100644
--- a/src/app/authorization-request/authorization-request.component.html
+++ b/src/app/authorization-request/authorization-request.component.html
@@ -15,10 +15,18 @@
<div class="card-body">
<strong>{{ oidcService.clientName }}</strong>
asks you to share personal information.<br/>
- Choose an identity to let it access the following information:
+ Choose an identity to let it access the following self-asserted
information:
<ul>
<li *ngFor="let attribute of getScopes()"><strong>{{attribute}}</strong>
</ul>
+ as well as the following third-party attested attributes:
+ <ul *ngIf="clientNameFound">
+ <ng-container *ngFor="let reference of getRefScope()">
+ <li *ngIf="reference[1] === true"><strong>{{reference[0]}}</strong>
+ <li *ngIf="reference[1] !== true"><strong>{{reference[0]}}
(optional)</strong>
+ </ng-container>
+ </ul>
+
</div>
<div style="margin: 1em;">
<button class="btn btn-danger m-1 mt-4" *ngIf="isClientVerified()"
(click)="cancelRequest()" style="margin-bottom: -4%;">
diff --git a/src/app/authorization-request/authorization-request.component.ts
b/src/app/authorization-request/authorization-request.component.ts
index ce0c7d1..72325e9 100644
--- a/src/app/authorization-request/authorization-request.component.ts
+++ b/src/app/authorization-request/authorization-request.component.ts
@@ -20,6 +20,10 @@ export class AuthorizationRequestComponent implements OnInit
{
return this.oidcService.getScope();
}
+ getRedSCope() {
+ return this.oidcService.getRefScope();
+ }
+
isClientVerified() {
return this.oidcService.isClientVerified();
}
diff --git a/src/app/edit-attestations/edit-attestations.component.css
b/src/app/edit-attestations/edit-attestations.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/edit-attestations/edit-attestations.component.html
b/src/app/edit-attestations/edit-attestations.component.html
new file mode 100644
index 0000000..6a8611f
--- /dev/null
+++ b/src/app/edit-attestations/edit-attestations.component.html
@@ -0,0 +1,83 @@
+<!-- Identity edit screen -->
+<div class="m-2 card">
+ <div class="card-avatar card-img-top">
+ <div class="card-avatar-character text-dark">
+ Manage attestations for <i>{{ identity.name }}</i>
+ </div>
+ </div>
+ <!-- Attestation management -->
+ <div class="card-body">
+ <h6 class="card-subtitle mb-2">Attestations:</h6>
+ <!-- Requested attestation -->
+ <table class="table pb-1" style="">
+ <thead>
+ <tr>
+ <th>Attestation Name</th>
+ <th>Attestation Type</th>
+ <th>Attestation Value</th>
+ <th>Isser</th>
+ <th>Attestation ID</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr [class.alert-danger]="newAttestation.name === attestation.name"
[class.alert-warning]="!isAttestationValid(attestation.id)" *ngFor="let
attestation of attestations">
+ <td><div style="min-width: 15em">{{attestation.name}}</div></td>
+ <td>
+ <div>{{attestation.type}}</div>
+ </td>
+ <td>
+ <input placeholder="Attestation Value"
[(ngModel)]="attestation.value">
+ </td>
+ <td>
+ <div style="min-width: 15em">
+ {{attestation_val[attestation.id]['iss']}}
+ </div>
+ </td>
+ <td><div style="min-width: 15em">{{attestation.id}}</div></td>
+ <td>
+ <button class="btn btn-primary"
(click)="deleteAttestation(attestation)">
+ <span class="fa fa-trash"></span>
+ </button>
+ </td>
+ <td>
+ <div *ngIf= "isAttestationValid(attestation.id)"> Valid <span
class="fa fa-check"></span> </div>
+ <div *ngIf= "!isAttestationValid(attestation.id)"> <span
style="color:#f00"> Expired </span> <span class="fa fa-times"></span> </div>
+ </td>
+ </tr>
+ <tr [class.alert-danger]="isAttestInConflict(newAttestation)">
+ <td>
+ <input [class.text-danger]="!attestationNameValid(newAttestation)"
placeholder="Attestation" [(ngModel)]="newAttestation.name">
+ </td>
+ <td>
+ <input placeholder="Type [JWT]"
[class.text-danger]="!attestationTypeValid(newAttestation)"
[(ngModel)]="newAttestation.type">
+ </td>
+ <td>
+ <input placeholder="Attestation Value"
[class.text-danger]="!attestationValueValid(newAttestation)"
[(ngModel)]="newAttestation.value">
+ </td>
+ <td>
+ <button [disabled]="!canAddAttestation(newAttestation)" class="btn
btn-primary" (click)="addAttestation(newAttestation)">
+ <span class="fa fa-plus"></span>
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <!-- Attestation creation warning -->
+ <div *ngIf="!attestationNameValid(newAttestation) ||
!attestationTypeValid(newAttestation) ||
!attestationValueValid(newAttestation)" class="alert alert-primary
alert-dismissible fade show" role="alert">
+ <span class="fa fa-warning"></span> Note:
+ <ul>
+ <li>Only use alphanumeric attestation names.</li>
+ <li>You cannot define the same name twice.</li>
+ <li>Types and values may not be empty!</li>
+ </ul>
+ </div>
+
+ <!-- Edit card buttons -->
+ <div>
+ <button class="btn btn-primary" (click)="saveAttestation()"
[disabled]="!canSaveAttestation()">
+ <span class="fa fa-save"></span> Save and Back
+ </button>
+ </div>
+ </div>
+</div>
+
diff --git a/src/app/edit-attestations/edit-attestations.component.ts
b/src/app/edit-attestations/edit-attestations.component.ts
new file mode 100644
index 0000000..77e23fd
--- /dev/null
+++ b/src/app/edit-attestations/edit-attestations.component.ts
@@ -0,0 +1,182 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { ReclaimService } from '../reclaim.service';
+import { Identity } from '../identity';
+import { Attestation } from '../attestation';
+import { IdentityService } from '../identity.service';
+import { from, forkJoin, EMPTY } from 'rxjs';
+import { finalize } from 'rxjs/operators';
+
+@Component({
+ selector: 'app-edit-attestations',
+ templateUrl: './edit-attestations.component.html',
+ styleUrls: ['./edit-attestations.component.css']
+})
+export class EditAttestationsComponent implements OnInit {
+
+ identity: Identity;
+ attestations: Attestation[];
+ newAttestation: Attestation;
+
+ constructor(private reclaimService: ReclaimService,
+ private identityService: IdentityService,
+ private activatedRoute: ActivatedRoute,
+ private router: Router) { }
+
+ ngOnInit() {
+ this.newAttestation = new Attestation('', '', '', '');
+ this.identity = new Identity('','');
+ this.activatedRoute.params.subscribe(p => {
+ if (p['id'] === undefined) {
+ return;
+ }
+ this.identityService.getIdentities().subscribe(
+ ids => {
+ for (let i = 0; i < ids.length; i++) {
+ if (ids[i].name == p['id']) {
+ this.identity = ids[i];
+ this.updateAttestation();
+ }
+ }
+ });
+ });
+
+ }
+
+ private updateAttestation() {
+ this.reclaimService.getAttestation(this.identity).subscribe(attestation =>
{
+ this.attestations = [];
+ let i;
+ for (i = 0; i < attestation.length; i++) {
+ this.attestations.push(attestation[i]);
+ }
+ },
+ err => {
+ //this.errorInfos.push("Error retrieving attestation for ``" +
identity.name + "''");
+ console.log(err);
+ });
+ }
+
+ addAttestation() {
+ this.storeAttestation()
+ .pipe(
+ finalize(() => {
+ this.newAttestation.name = '';
+ this.newAttestation.type = '';
+ this.newAttestation.value = '';
+ this.updateAttestation();
+ }))
+ .subscribe(res => {
+ console.log(res);
+ },
+ err => {
+ console.log(err);
+ //this.errorInfos.push("Failed to update identity ``" +
this.identityInEdit.name + "''");
+ EMPTY
+ });
+ }
+
+ private storeAttestation() {
+ const promises = [];
+ let i;
+ if (undefined !== this.attestations) {
+ for (i = 0; i < this.attestations.length; i++) {
+ promises.push(
+ from(this.reclaimService.addAttestation(this.identity,
this.attestations[i])));
+ }
+ }
+ if ((this.newAttestation.value !== '') || (this.newAttestation.type !==
'')) {
+ promises.push(from(this.reclaimService.addAttestation(this.identity,
this.newAttestation)));
+ }
+ return forkJoin(promises);
+ }
+
+ canSaveAttestation() {
+ if (this.canAddAttestation(this.newAttestation)) {
+ return true;
+ }
+ return ((this.newAttestation.name === '') &&
+ (this.newAttestation.value === '') &&
+ (this.newAttestation.type === '')) &&
+ !this.isAttestInConflict(this.newAttestation);
+ }
+
+ isAttestInConflict(attestation: Attestation) {
+ let i;
+ if (undefined !== this.attestations) {
+ for (i = 0; i < this.attestations.length; i++) {
+ if (attestation.name === this.attestations[i].name) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ saveAttestation() {
+ this.storeAttestation()
+ .pipe(
+ finalize(() => {
+ this.newAttestation.name = '';
+ this.newAttestation.value = '';
+ this.newAttestation.type = '';
+ this.router.navigate(['/edit-identity', this.identity.name]);
+ }))
+ .subscribe(res => {
+ //FIXME success dialog/banner
+ this.updateAttestation();
+ },
+ err => {
+ console.log(err);
+ //this.errorInfos.push("Failed to update identity ``" +
this.identityInEdit.name + "''");
+ });
+ }
+
+
+ deleteAttestation(attestation: Attestation) {
+ this.reclaimService.deleteAttestation(this.identity, attestation)
+ .subscribe(res => {
+ //FIXME info dialog
+ this.updateAttestation();
+ },
+ err => {
+ //this.errorInfos.push("Failed to delete attestation ``" +
attestation.name + "''");
+ console.log(err);
+ });
+ }
+
+ canAddAttestation(attestation: Attestation) {
+ if ((attestation.name === '') || (attestation.value === '') ||
(attestation.type === '')) {
+ return false;
+ }
+ if (attestation.name.indexOf(' ') >= 0) {
+ return false;
+ }
+ return !this.isAttestInConflict(attestation);
+ }
+
+ attestationNameValid(attestation: Attestation) {
+ if (attestation.name === '' && attestation.value === '' &&
attestation.type === '') {
+ return true;
+ }
+ if (attestation.name.indexOf(' ') >= 0) {
+ return false;
+ }
+ if (!/^[a-zA-Z0-9-]+$/.test(attestation.name)) {
+ return false;
+ }
+ return !this.isAttestInConflict(attestation);
+ }
+
+ attestationTypeValid(attestation: Attestation) {
+ if (attestation.type === '') {
+ return attestation.name === '';
+ }
+ return true;
+ }
+
+ attestationValueValid(attestation: Attestation) {
+ return true;
+ }
+
+}
diff --git a/src/app/edit-authorizations/edit-authorizations.component.css
b/src/app/edit-authorizations/edit-authorizations.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/edit-authorizations/edit-authorizations.component.html
b/src/app/edit-authorizations/edit-authorizations.component.html
new file mode 100644
index 0000000..ddb918d
--- /dev/null
+++ b/src/app/edit-authorizations/edit-authorizations.component.html
@@ -0,0 +1,64 @@
+<!-- Identity edit screen -->
+<div class="m-2 card">
+ <div class="card-avatar card-img-top">
+ <div class="card-avatar-character text-dark">
+ Manage authorizations for <i>{{ identity.name }}</i>
+ </div>
+ </div>
+ <div class="card-body">
+ <!-- Authorized entities -->
+ <div style="margin-top: 1.5em;">
+ <table class="table pb-1">
+ <thead style="border-top-style: hidden">
+ <tr>
+ <th scope="col">
+ <h6 class="card-subtitle mb-2">
+ Authorized Entity:
+ </h6>
+ </th>
+ <th scope="col">
+ <h6 class="card-subtitle mb-2">
+ Shared attributes:
+ </h6>
+ </th>
+ <th scope="col"></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let ticket of tickets">
+ <td>
+ <div style="min-width: 15em">
+ {{getAudienceName(ticket)}}
+ </div>
+ </td>
+ <td>
+ <div style="min-width: 15em">
+ {{ticketAttributeMapper[ticket.audience]}}
+ </div>
+ </td>
+ <td>
+ <button class="btn btn-primary" *ngIf="showConfirmRevoke !=
ticket" (click)="confirmRevoke(ticket)">
+ <span class="fa fa-unlink"></span> Revoke
+ </button>
+ <div class="alert alert-danger fade show"
*ngIf="showConfirmRevoke == ticket">
+ Do you really want to revoke this authorization?<br/><br/>
+ <button class="btn btn-primary m-2"
(click)="revokeTicket(ticket)">
+ <span class="fa fa-check"></span> Yes
+ </button>
+ <button class="btn btn-primary m-2"
(click)="hideConfirmRevoke()">
+ <span class="fa fa-close"></span> No
+ </button>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!-- Edit card buttons -->
+ <div>
+ <button class="btn btn-primary" [routerLink]="['/edit-identity',
identity.name]">
+ <span class="fa fa-back"></span> Back to {{identity.name}}
+ </button>
+ </div>
+ </div>
+</div>
diff --git a/src/app/edit-authorizations/edit-authorizations.component.ts
b/src/app/edit-authorizations/edit-authorizations.component.ts
new file mode 100644
index 0000000..79b2efe
--- /dev/null
+++ b/src/app/edit-authorizations/edit-authorizations.component.ts
@@ -0,0 +1,144 @@
+import { Component, OnInit } from '@angular/core';
+import { Ticket } from '../ticket';
+import { Identity } from '../identity';
+import { Attribute } from '../attribute';
+import { ReclaimService } from '../reclaim.service';
+import { ActivatedRoute } from '@angular/router';
+import { IdentityService } from '../identity.service';
+import { GnsService } from '../gns.service';
+import { NamestoreService } from '../namestore.service';
+
+@Component({
+ selector: 'app-edit-authorizations',
+ templateUrl: './edit-authorizations.component.html',
+ styleUrls: ['./edit-authorizations.component.css']
+})
+export class EditAuthorizationsComponent implements OnInit {
+
+ tickets: Ticket[];
+ audienceNames: {};
+ identity: Identity;
+ ticketAttributeMapper: {};
+ attributes: Attribute[];
+ showConfirmRevoke: any;
+
+ constructor(private reclaimService: ReclaimService,
+ private activatedRoute: ActivatedRoute,
+ private identityService: IdentityService,
+ private gnsService: GnsService,
+ private namestoreService: NamestoreService) { }
+
+ ngOnInit() {
+ this.tickets = [];
+ this.identity = new Identity('','');
+ this.attributes = [];
+ this.audienceNames = {};
+ this.ticketAttributeMapper = {};
+ this.showConfirmRevoke = false;
+ this.activatedRoute.params.subscribe(p => {
+ if (p['id'] === undefined) {
+ return;
+ }
+ this.identityService.getIdentities().subscribe(
+ ids => {
+ for (let i = 0; i < ids.length; i++) {
+ if (ids[i].name == p['id']) {
+ this.identity = ids[i];
+ this.updateAttributes();
+ }
+ }
+ });
+ });
+
+ }
+
+ getAudienceName(ticket) {
+ if (undefined === this.audienceNames[ticket.audience]) {
+ return 'Unknown';
+ }
+ return this.audienceNames[ticket.audience];
+ }
+
+
+ private updateTickets() {
+ this.reclaimService.getTickets(this.identity).subscribe(tickets => {
+ this.tickets = [];
+ if (tickets === null) {
+ return;
+ }
+ this.tickets = tickets;
+ tickets.forEach(ticket => {
+ this.mapAudience(ticket);
+ this.mapAttributes(ticket);
+ });
+ },
+ err => {
+ //this.errorInfos.push("Unable to retrieve tickets for identity ``" +
identity.name + "''");
+ console.log(err);
+ });
+ }
+
+ private mapAttributes(ticket) {
+ this.namestoreService.getNames(this.identity).subscribe(names => {
+ this.ticketAttributeMapper[ticket.audience] = [];
+ names = names.filter(name => name.record_name ===
ticket.rnd.toLowerCase());
+ for (let i = 0; i < names.length; i++) {
+ names[i].data.forEach(record => {
+ if (record.record_type === 'RECLAIM_ATTR_REF') {
+ this.attributes
+ .filter(attr => attr.id === record.value)
+ .map(attr => {
+ this.ticketAttributeMapper[ticket.audience].push(attr.name);
+ });
+ }
+ });
+ }
+ });
+ }
+
+ hideConfirmRevoke() { this.showConfirmRevoke = null; }
+
+ confirmRevoke(ticket) { this.showConfirmRevoke = ticket;}
+
+ revokeTicket(ticket) {
+ this.reclaimService.revokeTicket(ticket).subscribe(
+ result => {
+ this.updateAttributes();
+ },
+ err => {
+ //this.errorInfos.push("Unable to revoke ticket.");
+ console.log(err);
+ });
+ }
+
+ private updateAttributes() {
+ this.reclaimService.getAttributes(this.identity).subscribe(attributes => {
+ this.attributes = [];
+ if (attributes === null) {
+ return;
+ }
+ this.attributes = attributes;
+ this.updateTickets();
+ },
+ err => {
+ //this.errorInfos.push("Error retrieving attributes for ``" +
identity.name + "''");
+ console.log(err);
+ });
+ }
+
+
+
+ private mapAudience(ticket) {
+ this.gnsService.getClientName(ticket.audience).subscribe(records => {
+ for (let i = 0; i < records.data.length; i++) {
+ if (records.data[i].record_type !== 'RECLAIM_OIDC_CLIENT') {
+ continue;
+ }
+ this.audienceNames[ticket.audience] = records.data[i].value;
+ break;
+ }
+ });
+ }
+
+
+}
diff --git a/src/app/edit-identity/edit-identity.component.html
b/src/app/edit-identity/edit-identity.component.html
index 0820c55..caf82ef 100644
--- a/src/app/edit-identity/edit-identity.component.html
+++ b/src/app/edit-identity/edit-identity.component.html
@@ -9,8 +9,48 @@
<div class="card-body">
<div>
<h6 class="card-subtitle mb-2">Attributes:</h6>
+ <!-- Missing references -->
+ <table class="table pb-1" *ngIf="isReferenceMissing()">
+ <thead>
+ <tr>
+ <th>Attribute Name</th>
+ <th>Attribute Value</th>
+ <th>Attestation ID</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr [class.openid]="inOpenIdFlow()"
[class.alert-danger]="newReference.name === missing.name" class="text-primary"
*ngFor="let missing of missingReferences">
+ <td><div style="min-width: 15em">{{missing.name}}</div></td>
+ <td>
+<select *ngIf="missing.ref_id !== ''"
(change)="missing.ref_value=$event.target.value">
+ <option value="">Select a Claim</option>
+ <option *ngFor="let claim of
objectKeys(attestation_val[missing.ref_id])" value={{claim}}>
+ {{claim}}
+ </option>
+ <option
value="">{{attestation_val[missing.ref_id]['iss']}}</option>
+ </select>
+ </td>
+ <td>
+ <select (change)="missing.ref_id=$event.target.value; ">
+ <option value="">Select an Attestation</option>
+ <option *ngFor="let attest of attestations" value={{attest.id}}>
+ {{attest.name}}
+ </option>
+ </select>
+ </td>
+ <td>
+ </td>
+ </tr>
+ </tbody>
+ </table>
<!-- Missing attributes -->
<table class="table pb-1" *ngIf="isAttributeMissing()">
+ <thead>
+ <tr>
+ <th>Attribute Name</th>
+ <th>Attribute Value</th>
+ </tr>
+ </thead>
<tbody>
<tr [class.openid]="inOpenIdFlow()"
[class.alert-danger]="newAttribute.name === missing.name" class="text-primary"
*ngFor="let missing of missingAttributes">
<td><div style="min-width: 15em">{{missing.name}}</div></td>
@@ -24,14 +64,20 @@
</table>
<!-- Requested attributes -->
<table class="table pb-1" style="">
+ <thead *ngIf="!isAttributeMissing()">
+ <tr>
+ <th>Attribute Name</th>
+ <th>Attribute Value</th>
+ </tr>
+ </thead>
<tbody>
- <tr [class.openid]="inOpenIdFlow()"
[class.text-primary]="isRequested(attribute)"
[class.alert-danger]="newAttribute.name === attribute.name" *ngFor="let
attribute of attributes">
+ <tr [class.openid]="inOpenIdFlow()"
[class.text-primary]="isRequested(attribute)"
[class.alert-danger]="newAttribute.name === attribute.name"
[class.text-secondary]="isAttestation(attribute)" *ngFor="let attribute of
attributes">
<td><div style="min-width: 15em">{{ attribute.name }}</div></td>
<td>
<input placeholder="Value" [(ngModel)]="attribute.value">
</td>
<td>
- <button class="btn btn-primary"
(click)="deleteAttribute(attribute)">
+ <button class="btn btn-primary"
(click)="deleteAttribute(attribute)" *ngIf="!isAttestation(attribute)">
<span class="fa fa-trash"></span>
</button>
</td>
@@ -61,65 +107,107 @@
<li>Attribute values may not be empty!</li>
</ul>
</div>
- <!-- Authorized entities -->
- <div style="margin-top: 1.5em;">
- <table class="table pb-1" *ngIf="showTickets">
- <thead style="border-top-style: hidden">
+ <div>
+ <button class="btn btn-primary" (click)="toggleShowRef()"
[style.float]="'right'">
+ <span *ngIf="showReferences"> Hide</span>
+ <span *ngIf="!showReferences"> Show</span>
+ reference management
+ </button>
+ </div>
+ <div style="margin-top: 1.5em;" *ngIf="showReferences">
+ <h6 class="card-subtitle mb-2">References:</h6>
+ <!-- Requested references -->
+ <table class="table pb-1" style="">
+ <thead *ngIf="!isReferenceMissing()">
<tr>
- <th scope="col">
- <h6 class="card-subtitle mb-2">
- Authorized Entities:
- </h6>
- </th>
- <th scope="col">
- <h6 class="card-subtitle mb-2">
- Shared attributes:
- </h6>
- </th>
- <th scope="col"></th>
+ <th>Attribute Name</th>
+ <th>Attribute Value</th>
+ <th>Attestation ID</th>
</tr>
</thead>
<tbody>
- <tr *ngFor="let ticket of tickets">
+ <tr [class.openid]="inOpenIdFlow()"
[class.text-primary]="isRefRequested(reference)"
[class.text-secondary]="isoptRefRequested(reference)"
[class.alert-danger]="newReference.name === reference.name"
[class.alert-warning]="!isAttestationValid(reference.ref_id)" *ngFor="let
reference of references">
+ <td><div style="min-width: 15em">{{reference.name}}</div></td>
<td>
- <div style="min-width: 15em">
- {{getAudienceName(ticket)}}
- </div>
+ <input placeholder="Claim Name Attestation"
[(ngModel)]="reference.ref_value">
</td>
<td>
- <div style="min-width: 15em">
- {{ticketAttributeMapper[ticket.audience]}}
- </div>
+ <input placeholder="Attestation ID"
[(ngModel)]="reference.ref_id">
</td>
<td>
- <button class="btn btn-primary" *ngIf="showConfirmRevoke !=
ticket" (click)="confirmRevoke(ticket)">
- <span class="fa fa-unlink"></span> Revoke
+ <button class="btn btn-primary"
(click)="deleteReference(reference)">
+ <span class="fa fa-trash"></span>
+ </button>
+ </td>
+ <td>
+ <div *ngIf= "isAttestationValid(reference.ref_id)"> Valid
Reference <span class="fa fa-check"></span> </div>
+ <div *ngIf= "!isAttestationValid(reference.ref_id)"> <span
style="color:#f00"> Expired Attestation </span><span class="fa
fa-times"></span> </div>
+ </td>
+
+ </tr>
+ <tr [class.alert-danger]="isRefInConflict(newReference)">
+ <td>
+ <input [class.text-danger]="!referenceNameValid(newReference)"
placeholder="Reference" [(ngModel)]="newReference.name">
+ </td>
+ <td>
+ <select *ngIf="newReference.ref_id !== ''"
(change)="newReference.ref_value=$event.target.value">
+ <option value="">Select a Claim</option>
+ <option *ngFor="let claim of
objectKeys(attestation_val[newReference.ref_id])" value={{claim}}>
+ {{claim}}
+ </option>
+ <option
value="">{{attestation_val[newReference.ref_id]['iss']}}</option>
+ </select>
+
+ </td>
+ <td>
+ <select (change)="newReference.ref_id=$event.target.value; ">
+ <option value="">Select an Attestation</option>
+ <option *ngFor="let attest of attestations" value={{attest.id}}>
+ {{attest.name}}
+ </option>
+ </select>
+ </td>
+
+ <td>
+ <button [disabled]="!canAddReference(newReference)" class="btn
btn-primary" (click)="addReference(newReference)">
+ <span class="fa fa-plus"></span>
</button>
- <div class="alert alert-danger fade show"
*ngIf="showConfirmRevoke == ticket">
- Do you really want to revoke this authorization?<br/><br/>
- <button class="btn btn-primary m-2"
(click)="revokeTicket(ticket)">
- <span class="fa fa-check"></span> Yes
- </button>
- <button class="btn btn-primary m-2"
(click)="hideConfirmRevoke()">
- <span class="fa fa-close"></span> No
- </button>
- </div>
</td>
</tr>
</tbody>
</table>
</div>
+ <!-- optional attributes information -->
+ <div *ngIf="optionalReferences.length !== 0" class="text-primary">
+ <span class="fa fa-openid"></span> Optionally requested is also:
+ <ul>
+ <li *ngFor="let reference of
optionalReferences"><strong>{{reference}}</strong>
+ </ul>
+ </div>
+ <!-- Reference creation warning -->
+ <div *ngIf="!referenceNameValid(newReference) ||
!referenceValueValid(newReference) || !referenceIDValid(newReference)"
class="alert alert-primary alert-dismissible fade show" role="alert">
+ <span class="fa fa-warning"></span> Note:
+ <ul>
+ <li>Only use alphanumeric reference names.</li>
+ <li>You cannot define the same name twice.</li>
+ <li>IDs and values may not be empty!</li>
+ </ul>
+ </div>
+
<!-- Edit card buttons -->
<div>
- <button class="btn btn-primary" (click)="saveIdentityAttributes()"
[disabled]="!canSaveIdentity()">
+ <button class="btn btn-primary" (click)="saveIdentity()"
[disabled]="!canSaveIdentity()">
<span class="fa fa-save"></span> Save and Back
</button>
- <button *ngIf="(0 < tickets.length) && !inOpenIdFlow()" class="btn
btn-primary" (click)="toggleShowTickets()" [style.float]="'right'">
+ <button *ngIf="!inOpenIdFlow()" class="btn btn-primary"
[routerLink]="['/edit-authorizations', identity.name]" [style.float]="'right'">
<span class="fa fa-openid"></span>
- <span *ngIf="showTickets"> Hide</span>
- <span *ngIf="!showTickets"> Show</span>
- authorizations
+ Manage authorizations
</button>
+ <button *ngIf="!inOpenIdFlow()" class="btn btn-primary"
[routerLink]="['/edit-attestations', identity.name]" [style.float]="'right'">
+ <span class="fa fa-openid"></span>
+ Manage attestations
+ </button>
+
</div>
</div>
</div>
diff --git a/src/app/edit-identity/edit-identity.component.ts
b/src/app/edit-identity/edit-identity.component.ts
index 464237f..37f697d 100644
--- a/src/app/edit-identity/edit-identity.component.ts
+++ b/src/app/edit-identity/edit-identity.component.ts
@@ -1,12 +1,13 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ReclaimService } from '../reclaim.service';
-import { Ticket } from '../ticket';
import { Identity } from '../identity';
import { GnsService } from '../gns.service';
import { NamestoreService } from '../namestore.service';
import { OpenIdService } from '../open-id.service';
import { Attribute } from '../attribute';
+import { Attestation } from '../attestation';
+import { Reference } from '../reference';
import { IdentityService } from '../identity.service';
import { finalize } from 'rxjs/operators';
import { from, forkJoin, EMPTY } from 'rxjs';
@@ -18,16 +19,19 @@ import { from, forkJoin, EMPTY } from 'rxjs';
})
export class EditIdentityComponent implements OnInit {
- tickets: Ticket[];
identity: Identity;
- audienceNames: {};
- showTickets: Boolean;
- ticketAttributeMapper: {};
+ showReferences: Boolean;
attributes: Attribute[];
+ attestations: Attestation[];
+ attestationValues: {};
requestedAttributes: Attribute[];
missingAttributes: Attribute[];
newAttribute: Attribute;
- showConfirmRevoke: any;
+ references: Reference[];
+ newReference: Reference;
+ missingReferences: Reference[];
+ requestedReferences: Reference[];
+ optionalReferences: Reference[];
constructor(private reclaimService: ReclaimService,
private identityService: IdentityService,
@@ -38,14 +42,14 @@ export class EditIdentityComponent implements OnInit {
private router: Router) { }
ngOnInit() {
- this.tickets = [];
this.attributes = [];
- this.showConfirmRevoke = null;
- this.audienceNames = {};
+ this.attestations = [];
+ this.optionalReferences = [];
+ this.attestationValues = {};
this.identity = new Identity('','');
- this.newAttribute = new Attribute('', '', '', 'STRING');
- this.ticketAttributeMapper = {};
- this.activatedRoute.params.subscribe(p => {
+ this.newAttribute = new Attribute('', '', '', 'STRING', '');
+ this.newReference = new Reference('', '', '', '');
+ this.activatedRoute.params.subscribe(p => {
if (p['id'] === undefined) {
return;
}
@@ -55,79 +59,16 @@ export class EditIdentityComponent implements OnInit {
if (ids[i].name == p['id']) {
this.identity = ids[i];
this.updateAttributes();
+ this.updateReferences();
+ this.updateAttestations();
}
}
});
});
}
- confirmRevoke(ticket) { this.showConfirmRevoke = ticket;}
- hideConfirmRevoke() { this.showConfirmRevoke = null; }
- private mapAudience(ticket) {
- this.gnsService.getClientName(ticket.audience).subscribe(records => {
- for (let i = 0; i < records.data.length; i++) {
- if (records.data[i].record_type !== 'RECLAIM_OIDC_CLIENT') {
- continue;
- }
- this.audienceNames[ticket.audience] = records.data[i].value;
- break;
- }
- });
- }
-
- private updateTickets() {
- this.reclaimService.getTickets(this.identity).subscribe(tickets => {
- this.tickets = [];
- if (tickets === null) {
- return;
- }
- this.tickets = tickets;
- tickets.forEach(ticket => {
- this.mapAudience(ticket);
- this.mapAttributes(ticket);
- });
- },
- err => {
- //this.errorInfos.push("Unable to retrieve tickets for identity ``" +
identity.name + "''");
- console.log(err);
- });
- }
-
- private mapAttributes(ticket) {
- this.namestoreService.getNames(this.identity).subscribe(names => {
- this.ticketAttributeMapper[ticket.audience] = [];
- names = names.filter(name => name.record_name ===
ticket.rnd.toLowerCase());
- for (let i = 0; i < names.length; i++) {
- names[i].data.forEach(record => {
- if (record.record_type === 'RECLAIM_ATTR_REF') {
- this.attributes
- .filter(attr => attr.id === record.value)
- .map(attr => {
- this.ticketAttributeMapper[ticket.audience].push(attr.name);
- });
- }
- });
- }
- });
- }
-
- toggleShowTickets(identity) {
- this.showTickets = !this.showTickets;
- }
-
- revokeTicket(ticket) {
- this.reclaimService.revokeTicket(ticket).subscribe(
- result => {
- this.updateAttributes();
- },
- err => {
- //this.errorInfos.push("Unable to revoke ticket.");
- console.log(err);
- });
- }
-
private updateAttributes() {
this.reclaimService.getAttributes(this.identity).subscribe(attributes => {
this.attributes = [];
@@ -144,7 +85,6 @@ export class EditIdentityComponent implements OnInit {
}
}
this.getMissingAttributes();
- this.updateTickets();
},
err => {
//this.errorInfos.push("Error retrieving attributes for ``" +
identity.name + "''");
@@ -177,7 +117,7 @@ export class EditIdentityComponent implements OnInit {
}
this.missingAttributes = [];
for (i = 0; i < scopes.length; i++) {
- const attribute = new Attribute('', '', '', 'STRING');
+ const attribute = new Attribute('', '', '', 'STRING', '');
attribute.name = scopes[i];
this.missingAttributes.push(attribute);
}
@@ -214,6 +154,11 @@ export class EditIdentityComponent implements OnInit {
}
canSaveIdentity() {
+ return (this.canSaveAttribute() &&
+ this.canSaveReference());
+ }
+
+ canSaveAttribute() {
if (this.canAddAttribute(this.newAttribute)) {
return true;
}
@@ -222,6 +167,43 @@ export class EditIdentityComponent implements OnInit {
!this.isInConflict(this.newAttribute);
}
+ canSaveReference() {
+ if (this.canAddReference(this.newReference)) {
+ return true;
+ }
+ return ((this.newReference.name === '') &&
+ (this.newReference.ref_value === '') &&
+ (this.newReference.ref_id === '')) &&
+ !this.isRefInConflict(this.newReference);
+ }
+
+
+ isRefInConflict(reference) {
+ let i;
+ if (undefined !== this.missingReferences) {
+ for (i = 0; i < this.missingReferences.length; i++) {
+ if (reference.name ===
+ this.missingReferences[i].name) {
+ return true;
+ }
+ }
+ }
+ if (undefined !== this.references) {
+ for (i = 0; i < this.references.length; i++) {
+ if (reference.name === this.references[i].name) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ saveIdentity() {
+ this.saveIdentityAttributes();
+ this.saveIdentityReferences();
+ }
+
saveIdentityAttributes() {
this.storeAttributes()
.pipe(
@@ -268,6 +250,9 @@ export class EditIdentityComponent implements OnInit {
}
if (undefined !== this.attributes) {
for (i = 0; i < this.attributes.length; i++) {
+ if (this.attributes[i].flag === '1') {
+ continue; //Is an attestation
+ }
promises.push(
from(this.reclaimService.addAttribute(this.identity,
this.attributes[i])));
}
@@ -329,11 +314,303 @@ export class EditIdentityComponent implements OnInit {
this.requestedAttributes.length;
}
- getAudienceName(ticket) {
- if (undefined === this.audienceNames[ticket.audience]) {
- return 'Unknown';
+ private saveIdentityReferences() {
+ this.storeReferences()
+ .pipe(
+ finalize(() => {
+ this.newReference.name = '';
+ this.newReference.ref_value = '';
+ this.newReference.ref_id = '';
+ }))
+ .subscribe(res => {
+ //FIXME success dialog/banner
+ this.updateReferences();
+ },
+ err => {
+ console.log(err);
+ //this.errorInfos.push("Failed to update identity ``" +
this.identityInEdit.name + "''");
+ });
+ }
+
+ deleteReference(reference) {
+ this.reclaimService.deleteReference(this.identity, reference)
+ .subscribe(res => {
+ //FIXME info dialog
+ this.updateReferences();
+ this.updateAttributes();
+ },
+ err => {
+ //this.errorInfos.push("Failed to delete reference ``" +
reference.name + "''");
+ console.log(err);
+ });
+ }
+
+
+ getMissingReferences() {
+ const refscopes = this.oidcService.getRefScope();
+ let i;
+ for (i = 0; i < this.requestedReferences.length; i++) {
+ for (var j = 0; j < refscopes.length; j++) {
+ if (this.requestedReferences[i].name === refscopes[j][0] ) {
+ refscopes.splice(j,1);
+ }
+ }
+ }
+ this.missingReferences = [];
+ this.optionalReferences = [];
+ for (i = 0; i < refscopes.length; i++) {
+ const reference = new Reference('', '', '', '');
+ if (refscopes[i][1] === true)
+ {
+ reference.name = refscopes[i][0];
+ this.missingReferences.push(reference);
+ }
+ if (refscopes[i][1] === false)
+ {
+ reference.name = refscopes[i][0];
+ this.optionalReferences.push(reference);
+ }
}
- return this.audienceNames[ticket.audience];
+ }
+
+ toggleShowRef() {
+ this.showReferences = !this.showReferences;
+ }
+
+ private updateAttestations() {
+ this.reclaimService.getAttestation(this.identity).subscribe(attestations
=> {
+ this.attestations = attestations;
+ //FIXME this is not how this API should work
+ //The API should already return attributes which can be used...
+ let i;
+ for (i = 0; i < this.attestations.length; i++) {
+ this.reclaimService.parseAttest(this.attestations[i]).subscribe(values
=>{
+ this.attestationValues[this.attestations[i].id]=values;
+ },
+ err => {
+ //this.errorInfos.push("Error parsing attestation ``" +
attestation.name + "''");
+ console.log(err);
+ });
+
+ }
+ },
+ err => {
+ //this.errorInfos.push("Error retrieving attestation for ``" +
identity.name + "''");
+ console.log(err);
+ });
+ }
+
+ private updateReferences() {
+ this.reclaimService.getReferences(this.identity).subscribe(references => {
+ this.references = [];
+ this.requestedReferences = [];
+ if (references === null) {
+ this.getMissingReferences();
+ return;
+ }
+ const scope = this.oidcService.getRefScope();
+ let i;
+ for (i = 0; i < references.length; i++) {
+ this.references.push(references[i]);
+ let j;
+ for (j = 0; j < scope.length; j++) {
+ if (references[i].name === scope[j][0] ) {
+ this.requestedReferences.push(references[i]);
+ }
+ }
+ }
+ this.getMissingReferences();
+ },
+ err => {
+ //this.errorInfos.push("Error retrieving references for ``" +
identity.name + "''");
+ console.log(err);
+ });
+ }
+
+ private storeReferences() {
+ const promises = [];
+ let i;
+ if (undefined !== this.missingReferences) {
+ for (i = 0; i < this.missingReferences.length; i++) {
+ if ((this.missingReferences[i].ref_value === '') ||
(this.missingReferences[i].ref_id === '')) {
+ console.log("EmptyReferences: " + this.missingReferences[i]);
+ continue;
+ }
+ console.log("MissingReferences: " + this.missingReferences[i]);
+ promises.push(from(this.reclaimService.addReference(
+ this.identity, this.missingReferences[i])));
+ }
+ }
+ if (undefined !== this.references) {
+ for (i = 0; i < this.references.length; i++) {
+ promises.push(
+ from(this.reclaimService.addReference(this.identity,
this.references[i])));
+ }
+ }
+ if ((this.newReference.ref_value !== '') || (this.newReference.ref_id !==
'')) {
+ promises.push(from(this.reclaimService.addReference(this.identity,
this.newReference)));
+ }
+
+ return forkJoin(promises);
+ }
+
+ addReference() {
+ this.storeReferences()
+ .pipe(
+ finalize(() => {
+ this.newReference.name = '';
+ this.newReference.ref_value= '';
+ this.newReference.ref_id = '';
+ this.updateReferences();
+ this.updateAttributes();
+ }))
+ .subscribe(res => {
+ console.log(res);
+ },
+ err => {
+ console.log(err);
+ //this.errorInfos.push("Failed to update identity ``" +
this.identityInEdit.name + "''");
+ EMPTY
+ });
+ }
+
+
+ isAttestation(attribute) {
+ if (attribute.flag ==='1') {
+ return true;
+ }
+ return false;
+ }
+
+ canAddReference(reference) {
+ if ((reference.name === '') || (reference.ref_value === '') ||
(reference.ref_id === '')) {
+ return false;
+ }
+ if (reference.name.indexOf(' ') >= 0) {
+ return false;
+ }
+ return !this.isRefInConflict(reference);
+ }
+
+ referenceNameValid(reference) {
+ if (reference.name === '' && reference.ref_value === '' &&
reference.ref_id === '') {
+ return true;
+ }
+ if (reference.name.indexOf(' ') >= 0) {
+ return false;
+ }
+ if (!/^[a-zA-Z0-9-_]+$/.test(reference.name)) {
+ return false;
+ }
+ return !this.isRefInConflict(reference);
+ }
+
+ referenceValueValid(reference: Reference) {
+ if (reference.ref_value === '') {
+ return reference.name === '';
+ }
+ return true;
+ }
+
+ referenceIDValid(reference: Reference) {
+ if (reference.ref_id === '') {
+ return reference.name === '';
+ }
+ return true;
+ }
+
+
+ isRefRequested(reference: Reference) {
+ if (undefined === this.requestedReferences) {
+ return false;
+ } else {
+ return -1 !==
+ this.requestedReferences.indexOf(reference);
+ }
+ }
+
+ isAttrRefRequested(attribute: Attribute) {
+ if (undefined === this.requestedReferences) {
+ return false;
+ } else {
+ for (var j = 0; j < this.requestedReferences.length; j++) {
+ if (attribute.name === this.requestedReferences[j].name) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ isoptRefRequested(reference: Reference) {
+ if (undefined === this.optionalReferences) {
+ return false;
+ } else {
+ return -1 !==
+ this.optionalReferences.indexOf(reference);
+ }
+ }
+
+ isReferenceMissing() {
+ if (!this.inOpenIdFlow()) {
+ return false;
+ }
+ if (undefined === this.requestedReferences) {
+ return false;
+ }
+ for (var i = 0; i < this.oidcService.getRefScope().length; i++) {
+ if (this.oidcService.getRefScope()[i][1] === true) {
+ var j;
+ for (j = 0; j < this.requestedReferences.length; j++) {
+ if (this.oidcService.getRefScope()[i][0] ===
this.requestedReferences[j].name){
+ break;
+ }
+ }
+ if (j === this.requestedReferences.length){
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /*private setAttestationValue(attestation) {
+ var value_string="";
+ return this.reclaimService.parseAttest(attestation).subscribe(json_string
=>{
+ this.attestation_val[attestation.id]=json_string;
+ },
+ err => {
+ //this.errorInfos.push("Error parsing attestation ``" + attestation.name +
"''");
+ console.log(err);
+ });
+ }*/
+
+
+ isAttestationValid(attestation: Attestation) {
+ //FIXME JWT specific
+ //FIXME the expiration of the JWT should be a property of the attestation
+ //Not part of the values
+ const now = Date.now().valueOf() / 1000;
+ if (this.attestationValues[attestation.id]['exp'] === 'undefined') {
+ return true;
+ }
+ return this.attestationValues[attestation.id]['exp'] > now;
+ }
+
+
+ isAnyAttestationInvalid() {
+ if (!this.inOpenIdFlow()) {
+ return false;
+ }
+ if (undefined === this.requestedReferences) {
+ return false;
+ }
+ for (var j = 0; j < this.attestations.length; j++) {
+ if (!this.isAttestationValid(this.attestations[j])) {
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/src/app/identity-list/identity-list.component.html
b/src/app/identity-list/identity-list.component.html
index 40e95ba..18c9ffb 100644
--- a/src/app/identity-list/identity-list.component.html
+++ b/src/app/identity-list/identity-list.component.html
@@ -76,15 +76,25 @@
<div class="alert alert-secondary fade show"
*ngIf="!hasAttributes(identity)">
This identity has no attributes. Maybe try <a class="buttonlink"
[routerLink]="['/edit-identity', identity.name]">adding some?</a>
</div>
- <div *ngIf="isAttributeMissing(identity)" class="alert alert-danger
alert-dismissible fade show" role="alert">
- <span class="fa fa-openid"></span> This identity cannot be used because
it's missing requested attributes:
+ <div *ngIf="isAttributeMissing(identity) || isReferenceMissing(identity)"
class="alert alert-danger alert-dismissible fade show" role="alert">
+ <span class="fa fa-openid"></span> This identity cannot be used because
it's missing requested information:
<ul>
<li *ngFor="let attr of getMissing(identity)">{{attr}}</li>
+ <li *ngFor="let ref of getOptional(identity)">{{ref}}(optional)</li>
</ul>
<button class="btn btn-primary" [routerLink]="['/edit-identity',
identity.name]">
<span class="fa fa-plus"></span> Add
</button>
</div>
+ <div *ngIf="isReqReferenceInvalid(identity)" class="alert alert-danger
alert-dismissible fade show" role="alert">
+ <span class="fa fa-openid"></span> This identity cannot be used because
one or more references are invalid.
+ <button class="btn btn-primary" (click)="editIdentity(identity)">
+ <span class="fa fa-pencil-square-o"></span> Edit
+ </button>
+ </div>
+ <div *ngIf="(0 < requestedReferences[identity.pubkey]?.length) &&
inOpenIdFlow()" class="alert alert-danger alert-dismissible fade show"
role="alert">
+ <span class="fa fa-openid"></span> Please note that sharing references
might disclose additional information stored in the attestation.
+ </div>
<div class="card-body">
<!-- Attribute table -->
<div>
@@ -93,10 +103,11 @@
</h6>
<table class="table pb-1">
<tbody>
- <tr [class.openid]="inOpenIdFlow()"
[class.text-primary]="isRequested(identity, attribute)"
+ <tr [class.openid]="inOpenIdFlow()"
[class.text-primary]="isRequested(identity, attribute) ||
isAttrRefRequested(identity, attribute)"
+ [class.text-secondary]="!isAttrRefRequested(identity,
attribute) && isAttestation(attribute)"
*ngFor="let attribute of attributes[identity.pubkey]">
<td>
- <div style="min-width: 15em"><span
*ngIf="isRequested(identity, attribute)" class="fa fa-openid"></span>
{{attribute.name}}</div>
+ <div style="min-width: 15em"><span
*ngIf="isRequested(identity, attribute) || isAttrRefRequested(identity,
attribute)" class="fa fa-openid"></span> {{attribute.name}}</div>
</td>
<td>
<div *ngIf="attribute.value.length <= 20" style="min-width:
15em">{{attribute.value}}</div>
@@ -112,7 +123,7 @@
<div>
<div>
- <button *ngIf="canAuthorize(identity)" [disabled]="!inOpenIdFlow()
|| isAttributeMissing(identity) || !isClientVerified()"
(click)="loginIdentity(identity)" class="btn btn-primary mr-1 openid-login">
+ <button *ngIf="canAuthorize(identity)" [disabled]="!inOpenIdFlow()
|| isAttributeMissing(identity) || isReferenceMissing(identity) ||
!isClientVerified() || isReqReferenceInvalid(identity)"
(click)="loginIdentity(identity)" class="btn btn-primary mr-1 openid-login">
<span *ngIf="isClientVerified()"><i class="fa fa-openid"></i>
Share from this identity</span>
<span *ngIf="!isClientVerified()"><i class="fa
fa-exclamation-circle"></i> Sharing disabled</span>
</button>
diff --git a/src/app/identity-list/identity-list.component.ts
b/src/app/identity-list/identity-list.component.ts
index e0c0201..ee28486 100644
--- a/src/app/identity-list/identity-list.component.ts
+++ b/src/app/identity-list/identity-list.component.ts
@@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Attribute } from '../attribute';
+import { Reference } from '../reference';
import { GnsService } from '../gns.service';
import { Identity } from '../identity';
import { IdentityService } from '../identity.service';
@@ -21,8 +22,12 @@ import { from, forkJoin, EMPTY } from 'rxjs';
export class IdentityListComponent implements OnInit {
requestedAttributes: any;
+ requestedReferences: any;
missingAttributes: any;
+ missingReferences: any;
+ optionalReferences: any;
attributes: any;
+ references: any;
identities: Identity[];
showConfirmDelete: any;
connected: any;
@@ -41,10 +46,14 @@ export class IdentityListComponent implements OnInit {
ngOnInit() {
this.attributes = {};
+ this.references = {};
this.identities = [];
this.showConfirmDelete = null;
this.requestedAttributes = {};
this.missingAttributes = {};
+ this.requestedReferences = {};
+ this.missingReferences = {};
+ this.optionalReferences = {};
this.connected = false;
this.modalOpened = false;
if (!this.oidcService.inOpenIdFlow()) {
@@ -81,12 +90,66 @@ export class IdentityListComponent implements OnInit {
}
this.missingAttributes[identity.pubkey] = [];
for (i = 0; i < scopes.length; i++) {
- const attribute = new Attribute('', '', '', 'STRING');
+ const attribute = new Attribute('', '', '', 'STRING', '');
attribute.name = scopes[i];
this.missingAttributes[identity.pubkey].push(attribute);
}
}
+ getMissingReferences(identity) {
+ const refscopes = this.oidcService.getRefScope();
+ let i;
+ for (i = 0; i < this.requestedReferences[identity.pubkey].length; i++) {
+ for (var j = 0; j < refscopes.length; j++) {
+ if (this.requestedReferences[identity.pubkey][i].name ===
refscopes[j][0] ) {
+ refscopes.splice(j,1);
+ }
+ }
+ }
+ this.missingReferences[identity.pubkey] = [];
+ this.optionalReferences[identity.pubkey] = [];
+ for (i = 0; i < refscopes.length; i++) {
+ const reference = new Reference('', '', '', '');
+ if (refscopes[i][1] === true)
+ {
+ reference.name = refscopes[i][0];
+ this.missingReferences[identity.pubkey].push(reference);
+ }
+ if (refscopes[i][1] === false)
+ {
+ reference.name = refscopes[i][0];
+ this.optionalReferences[identity.pubkey].push(reference);
+ }
+ }
+ }
+
+ private updateReferences(identity) {
+ this.reclaimService.getReferences(identity).subscribe(references => {
+ this.references[identity.pubkey] = [];
+ this.requestedReferences[identity.pubkey] = [];
+ if (references === null) {
+ this.getMissingReferences(identity);
+ return;
+ }
+ const scope = this.oidcService.getRefScope();
+ let i;
+ for (i = 0; i < references.length; i++) {
+ this.references[identity.pubkey].push(references[i]);
+ let j;
+ for (j = 0; j < scope.length; j++) {
+ if (references[i].name === scope[j][0] ) {
+ this.requestedReferences[identity.pubkey].push(references[i]);
+ }
+ }
+ }
+ this.getMissingReferences(identity);
+ },
+ err => {
+ this.errorInfos.push("Error retrieving references for ``" +
identity.name + "''");
+ console.log(err);
+ });
+ }
+
private updateAttributes(identity) {
this.reclaimService.getAttributes(identity).subscribe(attributes => {
this.attributes[identity.pubkey] = [];
@@ -200,6 +263,7 @@ export class IdentityListComponent implements OnInit {
identities.forEach(identity => {
this.updateAttributes(identity);
+ this.updateReferences(identity);
});
if (!this.modalOpened) {
this.closeModal('GnunetInfo');
@@ -220,4 +284,52 @@ export class IdentityListComponent implements OnInit {
canSearch() {
return this.isConnected() && 0 != this.identities.length;
}
+
+ isReferenceMissing(identity) {
+ if (!this.inOpenIdFlow()) {
+ return false;
+ }
+ if (undefined === this.requestedReferences) {
+ return false;
+ }
+ for (var i = 0; i < this.oidcService.getRefScope().length; i++) {
+ if (this.oidcService.getRefScope()[i][1] === true) {
+ var j;
+ for (j = 0; j < this.requestedReferences.length; j++) {
+ if (this.oidcService.getRefScope()[i][0] ===
this.requestedReferences[j].name){
+ break;
+ }
+ }
+ if (j === this.requestedReferences.length){
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ isAttrRefRequested(identity: Identity, attribute: Attribute) {
+ if (undefined === this.requestedReferences[identity.pubkey]) {
+ return false;
+ } else {
+ for (var j = 0; j < this.requestedReferences[identity.pubkey].length;
j++) {
+ if (attribute.name ===
this.requestedReferences[identity.pubkey][j].name) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ isAttestation(attribute: Attribute) {
+ if (attribute.flag ==='1') {
+ return true;
+ }
+ return false;
+ }
+
+ isReqReferenceInvalid(identity: any) {
+ return false; //FIXME actually handle this
https://gitlab.com/voggenre/ui/commit/dd9b6656dee7dbf59809dcc9bc2508ee70d8afe6
+ }
+
}
diff --git a/src/app/open-id.service.ts b/src/app/open-id.service.ts
index a090c32..1151915 100644
--- a/src/app/open-id.service.ts
+++ b/src/app/open-id.service.ts
@@ -5,6 +5,7 @@ import { Identity } from './identity';
import { ConfigService } from './config.service';
import { Router } from '@angular/router';
import { GnsService } from './gns.service';
+import { Reference } from './reference';
@Injectable()
export class OpenIdService {
@@ -12,6 +13,7 @@ export class OpenIdService {
inOidcFlow: Boolean;
clientNameVerified: Boolean;
clientName: String;
+ referenceString: String;
constructor(private http: HttpClient,
private config: ConfigService,
@@ -19,8 +21,9 @@ export class OpenIdService {
private router: Router) {
this.params = {};
this.inOidcFlow = false;
+ this.referenceString = "";
}
-
+
getClientName() {
this.clientNameVerified = undefined;
if (!this.inOpenIdFlow()) {
@@ -66,12 +69,19 @@ export class OpenIdService {
window.location.href = this.config.get().apiUrl +
'/openid/authorize?client_id=' + this.params['client_id'] +
'&redirect_uri=' + this.params['redirect_uri'] +
'&response_type=' + this.params['response_type'] +
- '&scope=' + this.params['scope'] +
+ '&scope=' + this.params['scope'] + " " + this.referenceString +
'&state=' + this.params['state'] +
'&code_challenge=' + this.params['code_challenge'] +
'&nonce=' + this.params['nonce'];
}
+ setReferences(references: Reference[]) {
+ this.referenceString = "";
+ for(var i = 0; i < references.length; i++) {
+ this.referenceString = this.referenceString + references[i].name + " ";
+ }
+ }
+
cancelAuthorization(): any {
const httpOptions = {
withCredentials: true
@@ -101,4 +111,21 @@ export class OpenIdService {
scopes.splice(i, 1);
return scopes;
}
+
+ getRefScope(): any {
+ if (!this.inOpenIdFlow()) {
+ return [];
+ }
+ var scope = [];
+ var json = JSON.parse(this.params.get('claims'))['userinfo'];
+ for(var key in json)
+ {
+ if (json[key]['attestation'] === true)
+ {
+ scope.push([key, json[key]['essential'], json[key]['attestation'],
json[key]['format']]);
+ }
+ }
+ return scope;
+ }
+
}
diff --git a/src/app/reclaim.service.ts b/src/app/reclaim.service.ts
index ca8374e..94bb3b3 100644
--- a/src/app/reclaim.service.ts
+++ b/src/app/reclaim.service.ts
@@ -1,8 +1,10 @@
-import { HttpClient } from '@angular/common/http';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Attribute } from './attribute';
+import { Reference } from './reference';
+import { Attestation } from './attestation';
import { ConfigService } from './config.service';
import { GnuNetResponse } from './gnu-net-response';
import { Identity } from './identity';
@@ -38,4 +40,46 @@ export class ReclaimService {
return this.http.post<Ticket>(this.config.get().apiUrl + '/reclaim/revoke',
ticket);
}
+ getReferences(identity: Identity): Observable<Reference[]> {
+ return this.http.get<Reference[]>(this.config.get().apiUrl +
+ '/reclaim/attestation/reference/' + identity.name);
+ }
+
+ addReference(identity: Identity, reference: Reference) {
+ return this.http.post<Reference>(this.config.get().apiUrl +
+ '/reclaim/attestation/reference/' + identity.name,
+ reference);
+ }
+
+ deleteReference(identity: Identity, reference: Reference) {
+ const options = {headers: new HttpHeaders({'Content-Type':
'application/json',}),
+ body: reference,};
+ return this.http.delete(this.config.get().apiUrl +
'/reclaim/attestation/reference/' +
+ identity.name + '/' + reference.ref_id, options);
+ }
+
+ getAttestation(identity: Identity): Observable<Attestation[]> {
+ return this.http.get<Attestation[]>(this.config.get().apiUrl +
+ '/reclaim/attestation/' + identity.name);
+ }
+
+ addAttestation(identity: Identity, attestation: Attestation) {
+ return this.http.post<Attestation>(this.config.get().apiUrl +
+ '/reclaim/attestation/' + identity.name,
+ attestation);
+ }
+
+ deleteAttestation(identity: Identity, attestation: Attestation) {
+ return this.http.delete(this.config.get().apiUrl + '/reclaim/attestation/'
+
+ identity.name + '/' + attestation.id);
+ }
+
+ //FIXME this should be replaced by a data model that ties attributes
+ //and references to attestations together.
+ parseAttest(attestation: Attestation) {
+ var json = JSON.parse('{"value":"'+ attestation.value + '", "type":"'+
attestation.type + '"}')
+ return this.http.post(this.config.get().apiUrl +
+ '/reclaim/attestation/parse',json
+ );
+ }
}
diff --git a/src/app/reference.ts b/src/app/reference.ts
new file mode 100644
index 0000000..d135891
--- /dev/null
+++ b/src/app/reference.ts
@@ -0,0 +1,6 @@
+export class Reference {
+ constructor(public name: string,
+ public ref_value: string,
+ public id: string,
+ public ref_id: string) {}
+}
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [reclaim-ui] 178/459: handle optional refs, (continued)
- [reclaim-ui] 178/459: handle optional refs, gnunet, 2021/06/11
- [reclaim-ui] 184/459: fix save button, gnunet, 2021/06/11
- [reclaim-ui] 154/459: start extracting components, gnunet, 2021/06/11
- [reclaim-ui] 190/459: new api, gnunet, 2021/06/11
- [reclaim-ui] 164/459: update, gnunet, 2021/06/11
- [reclaim-ui] 182/459: actually add references to request, gnunet, 2021/06/11
- [reclaim-ui] 183/459: hack constant attestation button against local IdP, gnunet, 2021/06/11
- [reclaim-ui] 165/459: minor fixes, gnunet, 2021/06/11
- [reclaim-ui] 173/459: various fixed and improvements, gnunet, 2021/06/11
- [reclaim-ui] 199/459: Merge branch 'master' of gitlab.com:reclaimid/ui, gnunet, 2021/06/11
- [reclaim-ui] 172/459: manual merge from https://gitlab.com/voggenre/ui due to refactoring,
gnunet <=
- [reclaim-ui] 169/459: display name correctly, gnunet, 2021/06/11
- [reclaim-ui] 194/459: fix attest parsing, gnunet, 2021/06/11
- [reclaim-ui] 174/459: fix build, gnunet, 2021/06/11
- [reclaim-ui] 177/459: fix getter, gnunet, 2021/06/11
- [reclaim-ui] 168/459: actually initiate verify, gnunet, 2021/06/11
- [reclaim-ui] 197/459: link to fhg account, gnunet, 2021/06/11
- [reclaim-ui] 167/459: more refactoring and cleanup, gnunet, 2021/06/11
- [reclaim-ui] 175/459: fix, gnunet, 2021/06/11
- [reclaim-ui] 181/459: actually fix reference name display, gnunet, 2021/06/11
- [reclaim-ui] 200/459: webfinger, gnunet, 2021/06/11