feat: external idps on user (#755)

* feat: show external idps on user

* feat: show external idps on user

* fix: angular linting

* fix: display Name

* fix: display Name email
This commit is contained in:
Fabi 2020-09-18 17:00:38 +02:00 committed by GitHub
parent abf5151653
commit 108f6b3545
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 367 additions and 148 deletions

View File

@ -0,0 +1,49 @@
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
[timestamp]="externalIdpResult?.viewTimestamp" [selection]="selection">
<div class="table-wrapper">
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef>
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td mat-cell *matCellDef="let idp">
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(idp) : null" [checked]="selection.isSelected(idp)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="idpConfigId">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.EXTERNALIDP.IDPCONFIGID' | translate }} </th>
<td mat-cell *matCellDef="let idp"> {{idp?.idpConfigId}} </td>
</ng-container>
<ng-container matColumnDef="idpName">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.EXTERNALIDP.IDPNAME' | translate }} </th>
<td mat-cell *matCellDef="let idp"> {{idp?.idpName}} </td>
</ng-container>
<ng-container matColumnDef="externalUserDisplayName">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.EXTERNALIDP.USERDISPLAYNAME' | translate }} </th>
<td mat-cell *matCellDef="let idp"> {{idp?.externalUserDisplayName}} </td>
</ng-container>
<ng-container matColumnDef="externalUserId">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.EXTERNALIDP.EXTERNALUSERID' | translate }} </th>
<td mat-cell *matCellDef="let idp"> {{idp?.externalUserId}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;">
</tr>
</table>
<mat-paginator #paginator class="paginator" [length]="externalIdpResult?.totalResult || 0" [pageSize]="10"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></mat-paginator>
</div>
</app-refresh-table>

View File

@ -0,0 +1,39 @@
.table-wrapper {
overflow: auto;
.table,
.paginator {
width: 100%;
td,
th {
padding: 0 1rem;
&:first-child {
padding-left: 0;
padding-right: 1rem;
}
&:last-child {
padding-right: 0;
}
}
.data-row {
cursor: pointer;
&:hover {
background-color: #ffffff05;
}
}
}
}
tr {
outline: none;
}
.add-button {
border-radius: .5rem;
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ExternalIdpsComponent } from './external-idps.component';
describe('ExternalIdpsComponent', () => {
let component: ExternalIdpsComponent;
let fixture: ComponentFixture<ExternalIdpsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ExternalIdpsComponent ],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ExternalIdpsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,68 @@
import {Component, Input, OnInit, ViewChild} from '@angular/core';
import {MatPaginator, PageEvent} from '@angular/material/paginator';
import {MatTableDataSource} from '@angular/material/table';
import {
ExternalIDPSearchResponse,
ExternalIDPView,
} from '../../../../proto/generated/management_pb';
import {BehaviorSubject, Observable} from 'rxjs';
import {ManagementService} from '../../../../services/mgmt.service';
import {ToastService} from '../../../../services/toast.service';
import {SelectionModel} from '@angular/cdk/collections';
@Component({
selector: 'app-external-idps',
templateUrl: './external-idps.component.html',
styleUrls: ['./external-idps.component.scss'],
})
export class ExternalIdpsComponent implements OnInit {
@Input() userId!: string;
@ViewChild(MatPaginator) public paginator!: MatPaginator;
public externalIdpResult!: ExternalIDPSearchResponse.AsObject;
public dataSource: MatTableDataSource<ExternalIDPView.AsObject> = new MatTableDataSource<ExternalIDPView.AsObject>();
public selection: SelectionModel<ExternalIDPView.AsObject> = new SelectionModel<ExternalIDPView.AsObject>(true, []);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@Input() public displayedColumns: string[] = [ 'idpConfigId', 'idpName', 'externalUserId', 'externalUserDisplayName'];
constructor(private mgmtService: ManagementService,
private toast: ToastService) { }
ngOnInit(): void {
this.getData(10, 0);
}
public isAllSelected(): boolean {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
public masterToggle(): void {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
public changePage(event: PageEvent): void {
this.getData(event.pageSize, event.pageIndex * event.pageSize);
}
private async getData(limit: number, offset: number): Promise<void> {
this.loadingSubject.next(true);
this.mgmtService.SearchExternalIdps(this.userId, limit, offset).then(resp => {
this.externalIdpResult = resp.toObject();
this.dataSource.data = this.externalIdpResult.resultList;
console.log(this.externalIdpResult.resultList);
this.loadingSubject.next(false);
}).catch((error: any) => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
}
public refreshPage(): void {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
}
}

View File

@ -45,6 +45,7 @@ import { PasswordComponent } from './password/password.component';
import { UserDetailRoutingModule } from './user-detail-routing.module';
import { UserDetailComponent } from './user-detail/user-detail.component';
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
import { ExternalIdpsComponent } from './external-idps/external-idps.component';
@NgModule({
declarations: [
@ -58,6 +59,7 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
CodeDialogComponent,
MembershipsComponent,
MachineKeysComponent,
ExternalIdpsComponent,
],
imports: [
UserDetailRoutingModule,

View File

@ -43,6 +43,11 @@
</app-detail-form>
</app-card>
<app-card *ngIf="user.human && user.id" title="{{ 'USER.EXTERNALIDP.TITLE' | translate }}"
description="{{ 'USER.EXTERNALIDP.DESC' | translate }}">
<app-external-idps [userId]="user.id"></app-external-idps>
</app-card>
<app-card *ngIf="user.machine" title="{{ 'USER.MACHINE.TITLE' | translate }}">
<app-detail-form-machine
[disabled]="(['user.write:' + user?.id, 'user.write'] | hasRole | async) == false"
@ -191,4 +196,4 @@
<app-changes class="changes" [changeType]="ChangeType.USER" [id]="user.id"></app-changes>
</div>
</app-meta-layout>
</app-meta-layout>

View File

@ -3,153 +3,153 @@ import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import {
AddMachineKeyRequest,
AddMachineKeyResponse,
AddOrgDomainRequest,
AddOrgMemberRequest,
Application,
ApplicationID,
ApplicationSearchQuery,
ApplicationSearchRequest,
ApplicationSearchResponse,
ApplicationUpdate,
ApplicationView,
ChangeOrgMemberRequest,
ChangeRequest,
Changes,
CreateHumanRequest,
CreateMachineRequest,
CreateUserRequest,
Domain,
Gender,
GrantedProjectSearchRequest,
Iam,
Idp,
IdpID,
IdpProviderAdd,
IdpProviderID,
IdpProviderSearchRequest,
IdpProviderSearchResponse,
IdpProviderType,
IdpSearchQuery,
IdpSearchRequest,
IdpSearchResponse,
IdpView,
LoginName,
LoginPolicy,
LoginPolicyView,
MachineKeyIDRequest,
MachineKeySearchRequest,
MachineKeySearchResponse,
MachineKeyType,
MachineResponse,
MultiFactors,
NotificationType,
OIDCApplicationCreate,
OIDCConfig,
OIDCConfigUpdate,
OidcIdpConfig,
OidcIdpConfigCreate,
OidcIdpConfigUpdate,
Org,
OrgCreateRequest,
OrgDomain,
OrgDomainSearchQuery,
OrgDomainSearchRequest,
OrgDomainSearchResponse,
OrgDomainValidationRequest,
OrgDomainValidationResponse,
OrgDomainValidationType,
OrgIamPolicy,
OrgMember,
OrgMemberRoles,
OrgMemberSearchRequest,
OrgMemberSearchResponse,
OrgView,
PasswordAgePolicy,
PasswordAgePolicyCreate,
PasswordAgePolicyID,
PasswordAgePolicyUpdate,
PasswordComplexityPolicy,
PasswordComplexityPolicyCreate,
PasswordComplexityPolicyID,
PasswordComplexityPolicyUpdate,
PasswordLockoutPolicy,
PasswordLockoutPolicyCreate,
PasswordLockoutPolicyID,
PasswordLockoutPolicyUpdate,
PasswordRequest,
PrimaryOrgDomainRequest,
Project,
ProjectCreateRequest,
ProjectGrant,
ProjectGrantCreate,
ProjectGrantID,
ProjectGrantMember,
ProjectGrantMemberAdd,
ProjectGrantMemberChange,
ProjectGrantMemberRemove,
ProjectGrantMemberRoles,
ProjectGrantMemberSearchQuery,
ProjectGrantMemberSearchRequest,
ProjectGrantSearchRequest,
ProjectGrantSearchResponse,
ProjectGrantUpdate,
ProjectGrantView,
ProjectID,
ProjectMember,
ProjectMemberAdd,
ProjectMemberChange,
ProjectMemberRemove,
ProjectMemberRoles,
ProjectMemberSearchQuery,
ProjectMemberSearchRequest,
ProjectMemberSearchResponse,
ProjectRole,
ProjectRoleAdd,
ProjectRoleAddBulk,
ProjectRoleChange,
ProjectRoleRemove,
ProjectRoleSearchQuery,
ProjectRoleSearchRequest,
ProjectRoleSearchResponse,
ProjectSearchQuery,
ProjectSearchRequest,
ProjectSearchResponse,
ProjectUpdateRequest,
ProjectView,
RemoveOrgDomainRequest,
RemoveOrgMemberRequest,
SetPasswordNotificationRequest,
UpdateMachineRequest,
UpdateUserAddressRequest,
UpdateUserEmailRequest,
UpdateUserPhoneRequest,
UpdateUserProfileRequest,
UserAddress,
UserEmail,
UserGrant,
UserGrantCreate,
UserGrantID,
UserGrantRemoveBulk,
UserGrantSearchQuery,
UserGrantSearchRequest,
UserGrantSearchResponse,
UserGrantUpdate,
UserGrantView,
UserID,
UserMembershipSearchQuery,
UserMembershipSearchRequest,
UserMembershipSearchResponse,
UserPhone,
UserProfile,
UserResponse,
UserSearchQuery,
UserSearchRequest,
UserSearchResponse,
UserView,
ValidateOrgDomainRequest,
ZitadelDocs,
AddMachineKeyRequest,
AddMachineKeyResponse,
AddOrgDomainRequest,
AddOrgMemberRequest,
Application,
ApplicationID,
ApplicationSearchQuery,
ApplicationSearchRequest,
ApplicationSearchResponse,
ApplicationUpdate,
ApplicationView,
ChangeOrgMemberRequest,
ChangeRequest,
Changes,
CreateHumanRequest,
CreateMachineRequest,
CreateUserRequest,
Domain, ExternalIDPSearchRequest, ExternalIDPSearchResponse,
Gender,
GrantedProjectSearchRequest,
Iam,
Idp,
IdpID,
IdpProviderAdd,
IdpProviderID,
IdpProviderSearchRequest,
IdpProviderSearchResponse,
IdpProviderType,
IdpSearchQuery,
IdpSearchRequest,
IdpSearchResponse,
IdpView,
LoginName,
LoginPolicy,
LoginPolicyView,
MachineKeyIDRequest,
MachineKeySearchRequest,
MachineKeySearchResponse,
MachineKeyType,
MachineResponse,
MultiFactors,
NotificationType,
OIDCApplicationCreate,
OIDCConfig,
OIDCConfigUpdate,
OidcIdpConfig,
OidcIdpConfigCreate,
OidcIdpConfigUpdate,
Org,
OrgCreateRequest,
OrgDomain,
OrgDomainSearchQuery,
OrgDomainSearchRequest,
OrgDomainSearchResponse,
OrgDomainValidationRequest,
OrgDomainValidationResponse,
OrgDomainValidationType,
OrgIamPolicy,
OrgMember,
OrgMemberRoles,
OrgMemberSearchRequest,
OrgMemberSearchResponse,
OrgView,
PasswordAgePolicy,
PasswordAgePolicyCreate,
PasswordAgePolicyID,
PasswordAgePolicyUpdate,
PasswordComplexityPolicy,
PasswordComplexityPolicyCreate,
PasswordComplexityPolicyID,
PasswordComplexityPolicyUpdate,
PasswordLockoutPolicy,
PasswordLockoutPolicyCreate,
PasswordLockoutPolicyID,
PasswordLockoutPolicyUpdate,
PasswordRequest,
PrimaryOrgDomainRequest,
Project,
ProjectCreateRequest,
ProjectGrant,
ProjectGrantCreate,
ProjectGrantID,
ProjectGrantMember,
ProjectGrantMemberAdd,
ProjectGrantMemberChange,
ProjectGrantMemberRemove,
ProjectGrantMemberRoles,
ProjectGrantMemberSearchQuery,
ProjectGrantMemberSearchRequest,
ProjectGrantSearchRequest,
ProjectGrantSearchResponse,
ProjectGrantUpdate,
ProjectGrantView,
ProjectID,
ProjectMember,
ProjectMemberAdd,
ProjectMemberChange,
ProjectMemberRemove,
ProjectMemberRoles,
ProjectMemberSearchQuery,
ProjectMemberSearchRequest,
ProjectMemberSearchResponse,
ProjectRole,
ProjectRoleAdd,
ProjectRoleAddBulk,
ProjectRoleChange,
ProjectRoleRemove,
ProjectRoleSearchQuery,
ProjectRoleSearchRequest,
ProjectRoleSearchResponse,
ProjectSearchQuery,
ProjectSearchRequest,
ProjectSearchResponse,
ProjectUpdateRequest,
ProjectView,
RemoveOrgDomainRequest,
RemoveOrgMemberRequest,
SetPasswordNotificationRequest,
UpdateMachineRequest,
UpdateUserAddressRequest,
UpdateUserEmailRequest,
UpdateUserPhoneRequest,
UpdateUserProfileRequest,
UserAddress,
UserEmail,
UserGrant,
UserGrantCreate,
UserGrantID,
UserGrantRemoveBulk,
UserGrantSearchQuery,
UserGrantSearchRequest,
UserGrantSearchResponse,
UserGrantUpdate,
UserGrantView,
UserID,
UserMembershipSearchQuery,
UserMembershipSearchRequest,
UserMembershipSearchResponse,
UserPhone,
UserProfile,
UserResponse,
UserSearchQuery,
UserSearchRequest,
UserSearchResponse,
UserView,
ValidateOrgDomainRequest,
ZitadelDocs,
} from '../proto/generated/management_pb';
import { GrpcService } from './grpc.service';
@ -331,6 +331,18 @@ export class ManagementService {
return this.grpcService.mgmt.searchMachineKeys(req);
}
public async SearchExternalIdps(
userId: string,
limit: number,
offset: number,
asc?: boolean,
): Promise<ExternalIDPSearchResponse> {
const req = new ExternalIDPSearchRequest();
req.setUserId(userId);
req.setLimit(limit);
req.setOffset(offset);
return this.grpcService.mgmt.searchUserExternalIDPs(req);
}
public async GetIam(): Promise<Iam> {
const req = new Empty();
return this.grpcService.mgmt.getIam(req);

View File

@ -114,6 +114,14 @@
"OTP_DELETE_DESCRIPTION":"Sie sind im Begriff OTP als Zweitfaktormethode zu entfernen. Sind sie sicher?"
}
},
"EXTERNALIDP": {
"TITLE": "Externe Identitäts Provider",
"DESC": "",
"IDPCONFIGID": "Idp Konfig ID",
"IDPNAME": "Idp Name",
"USERDISPLAYNAME": "Externer Name",
"EXTERNALUSERID": "Externe Benutzer ID"
},
"CREATE": {
"TITLE": "Neuen Benutzer erstellen",
"DESCRIPTION": "Gebe die erforderlichen Daten ein.",

View File

@ -114,6 +114,14 @@
"OTP_DELETE_DESCRIPTION":"You are about to delete OTP as second factor. Are you sure?"
}
},
"EXTERNALIDP": {
"TITLE": "External Identity Providers",
"DESC": "",
"IDPCONFIGID": "Idp Config ID",
"IDPNAME": "Idp Name",
"USERDISPLAYNAME": "External Name",
"EXTERNALUSERID": "External User ID"
},
"CREATE": {
"TITLE": "Create a New User",
"DESCRIPTION": "Please provide the necesary information.",

View File

@ -188,6 +188,9 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) *model.ExternalUser {
displayName := tokens.IDTokenClaims.PreferredUsername
if displayName == "" && tokens.IDTokenClaims.Email != "" {
displayName = tokens.IDTokenClaims.Email
}
switch idpConfig.OIDCIDPDisplayNameMapping {
case iam_model.OIDCMappingFieldEmail:
if tokens.IDTokenClaims.EmailVerified && tokens.IDTokenClaims.Email != "" {