Skip to content

Commit

Permalink
Merge branch 'master' into production
Browse files Browse the repository at this point in the history
  • Loading branch information
xkostka2 committed Mar 28, 2022
2 parents 8413c2d + 96371ee commit aabbe84
Show file tree
Hide file tree
Showing 24 changed files with 502 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@
<td *matCellDef="let application" mat-cell>{{application.fedInfo}}</td>
</ng-container>

<ng-container *ngFor="let i = index; let col of parsedColumns;" matColumnDef="{{col}}">
<th *matHeaderCellDef mat-header-cell>{{col}}</th>
<td *matCellDef="let application" mat-cell>
{{getValue(application.formData, col)}}
</td>
</ng-container>

<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
<tr
*matRowDef="let application; columns: displayedColumns;"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { AfterViewInit, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core';
import {
Application,
ApplicationFormItemData,
ApplicationsOrderColumn,
AppState,
Group,
Member,
RichApplication,
Vo,
} from '@perun-web-apps/perun/openapi';
import {
Expand Down Expand Up @@ -77,6 +79,9 @@ export class ApplicationsDynamicListComponent implements OnInit, OnChanges, Afte
@Input()
refreshTable = false;

@Input()
parsedColumns: string[] = [];

dataSource: DynamicDataSource<Application>;

pageSizeOptions = TABLE_ITEMS_COUNT_OPTIONS;
Expand All @@ -98,6 +103,7 @@ export class ApplicationsDynamicListComponent implements OnInit, OnChanges, Afte
this.dynamicPaginatingService,
this.authResolver
);

this.dataSource.loadApplications(
this.tableConfigService.getTablePageSize(this.tableId),
0,
Expand All @@ -112,6 +118,16 @@ export class ApplicationsDynamicListComponent implements OnInit, OnChanges, Afte
this.group?.id ?? null,
this.getVoId()
);

this.dataSource.loading$.subscribe((val) => {
if (val || !this.displayedColumns.includes('fedInfo')) return;

this.displayedColumns = this.displayedColumns.filter((v) => !this.parsedColumns.includes(v));
this.parsedColumns = [];

const data = <RichApplication>this.dataSource.getData()[0];
this.parseColumns(data.formData);
});
}

ngOnChanges() {
Expand All @@ -136,7 +152,8 @@ export class ApplicationsDynamicListComponent implements OnInit, OnChanges, Afte
this.dateToString(this.dateTo),
this.member?.userId ?? null,
this.group?.id ?? null,
this.getVoId()
this.getVoId(),
true
);
}

Expand Down Expand Up @@ -195,6 +212,8 @@ export class ApplicationsDynamicListComponent implements OnInit, OnChanges, Afte
return data.type;
case 'fedInfo':
return data.fedInfo;
case 'formData':
return this.stringify((<RichApplication>data).formData);
case 'state':
return data.state;
case 'extSourceName':
Expand Down Expand Up @@ -273,4 +292,45 @@ export class ApplicationsDynamicListComponent implements OnInit, OnChanges, Afte
return this.member.voId;
}
}

stringify(obj: object) {
const removeNullUndefined = (toFilter: object) =>
Object.entries(toFilter).reduce(
(a, [k, v]) =>
a[k] instanceof Object
? (a[k] = removeNullUndefined(a[k]))
: v == null || v === 'null' || (<string>v).length === 0
? a
: ((a[k] = v), a),
{}
);

let str = JSON.stringify(removeNullUndefined(obj));
str = str.replace('{', '[');
str = str.replace('}', ']');
return str;
}

getFormDataString(data: ApplicationFormItemData) {
return this.stringify(data.formItem);
}

parseColumns(array: Array<ApplicationFormItemData>) {
array.forEach((val) => {
if (!this.displayedColumns.includes(val.shortname)) {
this.displayedColumns.push(val.shortname);
}
if (!this.parsedColumns.includes(val.shortname)) {
this.parsedColumns.push(val.shortname);
}
});
}

getValue(array: Array<ApplicationFormItemData>, colName: string) {
const filter = array.filter((value) => value.shortname === colName);
if (filter.length === 0) {
return '';
}
return filter[0].value ?? filter[0].prefilledValue;
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
<div class="mb-5" *ngIf="displayImageBlock">
<h1 class="page-subtitle">{{'AUTHENTICATION.TITLE' | customTranslate | translate}}</h1>
<p>{{'AUTHENTICATION.ANTI_PHISHING_INFO' | customTranslate | translate}}</p>
<div *ngIf="imageSrc && imageSrc.length">
<img [src]="imageSrc" alt="" class="img-size" />
<div [hidden]="loadingMfa || loadingImg">
<div class="mb-5" *ngIf="displayImageBlock">
<h1 class="page-subtitle">{{'AUTHENTICATION.TITLE' | customTranslate | translate}}</h1>
<p>{{'AUTHENTICATION.ANTI_PHISHING_INFO' | customTranslate | translate}}</p>
<div *ngIf="imageSrc && imageSrc.length">
<img [src]="imageSrc" alt="" class="img-size" />
</div>
<button (click)="onAddImg()" class="m-1 action-button" color="accent" mat-flat-button>
{{'AUTHENTICATION.NEW_IMG' | customTranslate | translate}}
</button>
<button
(click)="onDeleteImg()"
class="m-1"
color="warn"
[disabled]="!imgAtt || !imgAtt.value"
mat-flat-button>
{{'AUTHENTICATION.DELETE_IMG' | customTranslate | translate}}
</button>
</div>
<button (click)="onAddImg()" class="m-1 action-button" color="accent" mat-flat-button>
{{'AUTHENTICATION.NEW_IMG' | customTranslate | translate}}
</button>
<button
(click)="onDeleteImg()"
class="m-1"
color="warn"
[disabled]="!imgAtt || !imgAtt.value"
mat-flat-button>
{{'AUTHENTICATION.DELETE_IMG' | customTranslate | translate}}

<h1 class="page-subtitle">{{'AUTHENTICATION.MFA' | customTranslate | translate}}</h1>
<span
[matTooltip]="'AUTHENTICATION.MFA_DISABLED' | customTranslate | translate"
[matTooltipDisabled]="mfaAvailable"
matTooltipPosition="right">
<mat-slide-toggle
[disabled]="!mfaAvailable"
#toggle
color="primary"
>{{'AUTHENTICATION.MFA_TOGGLE' | customTranslate | translate}}</mat-slide-toggle
>
</span>

<br />
<button mat-flat-button class="mt-3" (click)="redirectToMfa()" color="accent">
{{'AUTHENTICATION.MFA_INFO'|translate}}
</button>
</div>

<h1 class="page-subtitle">{{'AUTHENTICATION.MFA' | customTranslate | translate}}</h1>
<span
>{{'AUTHENTICATION.MFA_INFO'|translate}}<a [href]="mfaUrl">{{mfaUrl}}</a></span
>
<mat-spinner class="ml-auto mr-auto" *ngIf="loadingMfa || loadingImg"></mat-spinner>
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
import { Component, OnInit } from '@angular/core';
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { getDefaultDialogConfig } from '@perun-web-apps/perun/utils';
import { AddAuthImgDialogComponent } from '../../../components/dialogs/add-auth-img-dialog/add-auth-img-dialog.component';
import { Attribute, AttributesManagerService } from '@perun-web-apps/perun/openapi';
import { StoreService } from '@perun-web-apps/perun/services';
import { AuthService, StoreService } from '@perun-web-apps/perun/services';
import { RemoveStringValueDialogComponent } from '../../../components/dialogs/remove-string-value-dialog/remove-string-value-dialog.component';
import { TranslateService } from '@ngx-translate/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { MatSlideToggle } from '@angular/material/slide-toggle';

@Component({
selector: 'perun-web-apps-settings-authentication',
templateUrl: './settings-authentication.component.html',
styleUrls: ['./settings-authentication.component.scss'],
})
export class SettingsAuthenticationComponent implements OnInit {
export class SettingsAuthenticationComponent implements OnInit, AfterViewInit {
@ViewChild('toggle') toggle: MatSlideToggle;

removeDialogTitle: string;
imgAtt: Attribute;
imageSrc = '';
removeDialogDescription: string;
mfaUrl = '';
displayImageBlock: boolean;
mfaAvailable = false;
mfaApiUrl = '';
loadingMfa = false;
loadingImg = false;

constructor(
private dialog: MatDialog,
private attributesManagerService: AttributesManagerService,
private store: StoreService,
private translate: TranslateService
private translate: TranslateService,
private oauthService: OAuthService,
private authService: AuthService
) {
translate
.get('AUTHENTICATION.DELETE_IMG_DIALOG_TITLE')
Expand All @@ -34,7 +44,15 @@ export class SettingsAuthenticationComponent implements OnInit {
.subscribe((res) => (this.removeDialogDescription = res));
}

ngAfterViewInit(): void {
this.toggle.change.subscribe((change) => {
this.reAuthenticate(change.checked);
});
}

ngOnInit(): void {
this.loadingMfa = true;
this.loadingImg = true;
this.translate.onLangChange.subscribe(() => {
this.translate
.get('AUTHENTICATION.DELETE_IMG_DIALOG_TITLE')
Expand All @@ -45,9 +63,58 @@ export class SettingsAuthenticationComponent implements OnInit {
this.mfaUrl = this.store.get('mfa', 'url_' + this.translate.currentLang);
});
this.mfaUrl = this.store.get('mfa', 'url_' + this.translate.currentLang);
this.mfaApiUrl = this.store.get('mfa', 'api_url');
fetch(this.mfaApiUrl + 'mfaAvailable', {
method: 'GET',
headers: { Authorization: 'Bearer ' + this.oauthService.getIdToken() },
})
.then((response) => response.text())
.then((responseText) => {
this.mfaAvailable = responseText === 'true';
if (this.mfaAvailable) {
this.loadMfa();
}
})
.catch((e) => {
console.error(e);
this.loadingMfa = false;
});

this.loadImage();
}

private loadMfa(): void {
const mfaRoute = sessionStorage.getItem('mfa_route');
if (mfaRoute) {
const enforceMfa = sessionStorage.getItem('enforce_mfa');
this.enableMfa(enforceMfa === 'true')
.then((res) => {
if (res.ok && enforceMfa === 'true') {
this.toggle.toggle();
}
this.loadingMfa = false;
})
.catch((e) => {
console.error(e);
this.loadingMfa = false;
});
} else {
const enforceMfaAttributeName = this.store.get('mfa', 'enforce_mfa_attribute');
this.attributesManagerService
.getUserAttributeByName(this.store.getPerunPrincipal().userId, enforceMfaAttributeName)
.subscribe((attr) => {
if (attr.value) {
this.toggle.toggle();
}
this.loadingMfa = false;
});
}
if (sessionStorage.getItem('mfa_route')) {
sessionStorage.removeItem('enforce_mfa');
sessionStorage.removeItem('mfa_route');
}
}

onAddImg() {
const config = getDefaultDialogConfig();
config.width = '500px';
Expand All @@ -62,13 +129,29 @@ export class SettingsAuthenticationComponent implements OnInit {
});
}

// private transformTextToImg(text: string) {
// const canvas = document.createElement('canvas');
// const context = canvas.getContext('2d');
// context.font = "100px Calibri";
// context.fillText(text, 1, 70);
// return canvas.toDataURL('image/png');
// }
reAuthenticate(enforceMfa: boolean): void {
sessionStorage.setItem('enforce_mfa', enforceMfa.toString());
sessionStorage.setItem('mfa_route', '/profile/settings/auth');
localStorage.removeItem('refresh_token');
this.oauthService.logOut(true);
sessionStorage.setItem('auth:redirect', location.pathname);
sessionStorage.setItem('auth:queryParams', location.search.substring(1));
this.authService.loadConfigData();
this.oauthService.loadDiscoveryDocumentAndLogin();
}

enableMfa(value: boolean): Promise<Response> {
const idToken = this.oauthService.getIdToken();
const path = `mfaEnforced`;
const url = `${this.mfaApiUrl}${path}`;
const body = `value=${value}`;

return fetch(url, {
method: 'PUT',
body: body,
headers: { Authorization: `Bearer ${idToken}` },
});
}

onDeleteImg() {
const config = getDefaultDialogConfig();
Expand All @@ -95,17 +178,28 @@ export class SettingsAuthenticationComponent implements OnInit {
this.displayImageBlock = this.store.get('mfa', 'enable_security_image');
this.attributesManagerService
.getUserAttributeByName(this.store.getPerunPrincipal().userId, imgAttributeName)
.subscribe((attr) => {
if (!attr) {
this.attributesManagerService
.getAttributeDefinitionByName(imgAttributeName)
.subscribe((att) => {
this.imgAtt = att as Attribute;
});
} else {
this.imgAtt = attr;
this.imageSrc = this.imgAtt.value as unknown as string;
.subscribe(
(attr) => {
if (!attr) {
this.attributesManagerService
.getAttributeDefinitionByName(imgAttributeName)
.subscribe((att) => {
this.imgAtt = att as Attribute;
});
} else {
this.imgAtt = attr;
this.imageSrc = this.imgAtt.value as unknown as string;
}
this.loadingImg = false;
},
(e) => {
console.error(e);
this.loadingImg = false;
}
});
);
}

redirectToMfa(): void {
window.open(this.mfaUrl, '_blank');
}
}
1 change: 1 addition & 0 deletions apps/user-profile/src/assets/config/defaultConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"api_url": "https://id.muni.cz/mfaapi/",
"enable_security_image": true,
"security_image_attribute": "urn:perun:user:attribute-def:def:securityImage:mu",
"enforce_mfa_attribute": "urn:perun:user:attribute-def:def:mfaEnforced:mu",
"url_en": "https://mfa.aai.muni.cz/",
"url_cs": "https://mfa.aai.muni.cz/"
},
Expand Down
Loading

0 comments on commit aabbe84

Please sign in to comment.