From 108f6b3545f3be9545c22a666c5b5806190d2d63 Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Fri, 18 Sep 2020 17:00:38 +0200 Subject: [PATCH] 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 --- .../external-idps.component.html | 49 +++ .../external-idps.component.scss | 39 +++ .../external-idps.component.spec.ts | 25 ++ .../external-idps/external-idps.component.ts | 68 ++++ .../users/user-detail/user-detail.module.ts | 2 + .../user-detail/user-detail.component.html | 7 +- console/src/app/services/mgmt.service.ts | 306 +++++++++--------- console/src/assets/i18n/de.json | 8 + console/src/assets/i18n/en.json | 8 + .../login/handler/external_login_handler.go | 3 + 10 files changed, 367 insertions(+), 148 deletions(-) create mode 100644 console/src/app/pages/users/user-detail/external-idps/external-idps.component.html create mode 100644 console/src/app/pages/users/user-detail/external-idps/external-idps.component.scss create mode 100644 console/src/app/pages/users/user-detail/external-idps/external-idps.component.spec.ts create mode 100644 console/src/app/pages/users/user-detail/external-idps/external-idps.component.ts diff --git a/console/src/app/pages/users/user-detail/external-idps/external-idps.component.html b/console/src/app/pages/users/user-detail/external-idps/external-idps.component.html new file mode 100644 index 000000000..1cdd1cec8 --- /dev/null +++ b/console/src/app/pages/users/user-detail/external-idps/external-idps.component.html @@ -0,0 +1,49 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + {{ 'USER.EXTERNALIDP.IDPCONFIGID' | translate }} {{idp?.idpConfigId}} {{ 'USER.EXTERNALIDP.IDPNAME' | translate }} {{idp?.idpName}} {{ 'USER.EXTERNALIDP.USERDISPLAYNAME' | translate }} {{idp?.externalUserDisplayName}} {{ 'USER.EXTERNALIDP.EXTERNALUSERID' | translate }} {{idp?.externalUserId}}
+ +
+ +
diff --git a/console/src/app/pages/users/user-detail/external-idps/external-idps.component.scss b/console/src/app/pages/users/user-detail/external-idps/external-idps.component.scss new file mode 100644 index 000000000..073164988 --- /dev/null +++ b/console/src/app/pages/users/user-detail/external-idps/external-idps.component.scss @@ -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; +} diff --git a/console/src/app/pages/users/user-detail/external-idps/external-idps.component.spec.ts b/console/src/app/pages/users/user-detail/external-idps/external-idps.component.spec.ts new file mode 100644 index 000000000..189f5459a --- /dev/null +++ b/console/src/app/pages/users/user-detail/external-idps/external-idps.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ExternalIdpsComponent ], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ExternalIdpsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/pages/users/user-detail/external-idps/external-idps.component.ts b/console/src/app/pages/users/user-detail/external-idps/external-idps.component.ts new file mode 100644 index 000000000..48b0a400d --- /dev/null +++ b/console/src/app/pages/users/user-detail/external-idps/external-idps.component.ts @@ -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 = new MatTableDataSource(); + public selection: SelectionModel = new SelectionModel(true, []); + private loadingSubject: BehaviorSubject = new BehaviorSubject(false); + public loading$: Observable = 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 { + 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); + } +} diff --git a/console/src/app/pages/users/user-detail/user-detail.module.ts b/console/src/app/pages/users/user-detail/user-detail.module.ts index f0a553bc0..6102d7149 100644 --- a/console/src/app/pages/users/user-detail/user-detail.module.ts +++ b/console/src/app/pages/users/user-detail/user-detail.module.ts @@ -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, diff --git a/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html b/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html index 0a7c97d81..4bb71de29 100644 --- a/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html +++ b/console/src/app/pages/users/user-detail/user-detail/user-detail.component.html @@ -43,6 +43,11 @@ + + + + - \ No newline at end of file + diff --git a/console/src/app/services/mgmt.service.ts b/console/src/app/services/mgmt.service.ts index 8dbf83a42..21b6f0fae 100644 --- a/console/src/app/services/mgmt.service.ts +++ b/console/src/app/services/mgmt.service.ts @@ -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 { + const req = new ExternalIDPSearchRequest(); + req.setUserId(userId); + req.setLimit(limit); + req.setOffset(offset); + return this.grpcService.mgmt.searchUserExternalIDPs(req); + } public async GetIam(): Promise { const req = new Empty(); return this.grpcService.mgmt.getIam(req); diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 7e86f334c..9bb3753a3 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -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.", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 1a68ff61b..606b75a94 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -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.", diff --git a/internal/ui/login/handler/external_login_handler.go b/internal/ui/login/handler/external_login_handler.go index 80265a94e..9665a6ce9 100644 --- a/internal/ui/login/handler/external_login_handler.go +++ b/internal/ui/login/handler/external_login_handler.go @@ -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 != "" {