You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@archiva.apache.org by ma...@apache.org on 2020/11/04 15:20:41 UTC

[archiva] 02/02: Adding permission handling to webapp

This is an automated email from the ASF dual-hosted git repository.

martin_s pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/archiva.git

commit 2313e1cd86e8091bfcfe074b77e698becea8790e
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Wed Nov 4 16:20:31 2020 +0100

    Adding permission handling to webapp
---
 .../main/archiva-web/src/app/app.component.html    |   6 +-
 .../src/main/archiva-web/src/app/app.component.ts  |   8 +-
 .../src/main/archiva-web/src/app/app.module.ts     |   2 +
 .../view-permission.directive.spec.ts}             |  20 +--
 .../app/directives/view-permission.directive.ts    |  64 +++++++++
 .../main/archiva-web/src/app/model/access-token.ts |   3 +-
 .../model/{access-token.ts => operation.spec.ts}   |  19 ++-
 .../app/model/{access-token.ts => operation.ts}    |  16 +--
 .../model/{access-token.ts => permission.spec.ts}  |  19 ++-
 .../app/model/{access-token.ts => permission.ts}   |  21 +--
 .../model/{access-token.ts => resource.spec.ts}    |  19 ++-
 .../src/app/model/{access-token.ts => resource.ts} |  15 +-
 .../main/archiva-web/src/app/model/user-info.ts    |   1 -
 .../general/sidemenu/sidemenu.component.html       |  16 ++-
 .../modules/general/sidemenu/sidemenu.component.ts |   7 +-
 .../src/app/services/authentication.service.ts     |  55 +++++--
 .../archiva-web/src/app/services/user.service.ts   | 159 ++++++++++++++++++++-
 17 files changed, 353 insertions(+), 97 deletions(-)

diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html
index f223be3..fd24fdc 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html
@@ -29,7 +29,7 @@
 
             <div class="collapse navbar-collapse" id="navbarsDefault">
                 <div class="navbar-nav ml-auto">
-                    <span *ngIf="auth.loggedIn" class="navbar-text border-right pr-2 mr-2">
+                    <span *ngIf="auth.authenticated" class="navbar-text border-right pr-2 mr-2">
                       {{user.userInfo.fullName}}
                     </span>
                     <ul class="navbar-nav">
@@ -38,12 +38,12 @@
                                 <i class="fas fa-home mr-1"></i>{{ 'menu.home' |translate }}
                             </a>
                         </li>
-                        <li *ngIf="!auth.loggedIn" class="nav-item active">
+                        <li *ngIf="!auth.authenticated" class="nav-item active">
                             <a class="nav-link" routerLink="/login" data-toggle="modal" data-target="#loginModal">
                                 <i class="fas fa-user mr-1"></i>{{'menu.login' | translate}}
                             </a>
                         </li>
-                        <li *ngIf="auth.loggedIn" class="nav-item active">
+                        <li *ngIf="auth.authenticated" class="nav-item active">
                             <a class="nav-link" routerLink="/logout" (click)="auth.logout()">
                                 <i class="fas fa-user mr-1"></i>{{'menu.logout' | translate}}
                             </a>
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts
index 3dae41b..e88a6a1 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.ts
@@ -62,17 +62,17 @@ export class AppComponent implements OnInit, OnDestroy{
 
 
   ngOnInit(): void {
-    let lang = this.user.userInfo.language;
-    if (lang==null) {
-      this.translate.use('en');
+    if (this.user.userInfo!=null && this.user.userInfo.language!=null ) {
+      this.translate.use(this.user.userInfo.language);
     } else {
-      this.translate.use(lang);
+      this.translate.use('en');
     }
     // Subscribe to login event in authenticator to switch the language
     this.auth.LoginEvent.subscribe(userInfo => {
       if (userInfo.language != null) {
         this.switchLang(userInfo.language);
       }
+      // console.log("Permissions: " + JSON.stringify(this.user.permissions));
     })
 
   }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts
index aa38842..451e095 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.module.ts
@@ -31,6 +31,7 @@ import { NotFoundComponent } from './modules/general/not-found/not-found.compone
 import { SidemenuComponent } from './modules/general/sidemenu/sidemenu.component';
 import {FormsModule, ReactiveFormsModule} from "@angular/forms";
 import { LoginComponent } from './modules/general/login/login.component';
+import { ViewPermissionDirective } from './directives/view-permission.directive';
 
 @NgModule({
   declarations: [
@@ -41,6 +42,7 @@ import { LoginComponent } from './modules/general/login/login.component';
     NotFoundComponent,
     SidemenuComponent,
     LoginComponent,
+    ViewPermissionDirective,
   ],
   imports: [
     BrowserModule,
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/view-permission.directive.spec.ts
similarity index 70%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/view-permission.directive.spec.ts
index 6204371..9c3bb9b 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/view-permission.directive.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,11 +15,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-export class AccessToken {
-    access_token: string;
-    refresh_token: string;
-    expires_in: number;
-    token_type: string;
-    scope: string;
-    state: string;
-}
+
+import { ViewPermissionDirective } from './view-permission.directive';
+
+describe('ViewPermissionDirective', () => {
+  it('should create an instance', () => {
+    const directive = new ViewPermissionDirective(null, null);
+    expect(directive).toBeTruthy();
+  });
+});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/view-permission.directive.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/view-permission.directive.ts
new file mode 100644
index 0000000..70aeadb
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/directives/view-permission.directive.ts
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {Directive, ElementRef, Input, OnChanges, OnInit, Renderer2, SimpleChanges} from '@angular/core';
+
+/**
+ * This directive can be used to render based on permissions
+ */
+@Directive({
+    selector: '[appViewPermission]'
+})
+export class ViewPermissionDirective implements OnInit, OnChanges {
+    @Input('appViewPermission') permission: boolean;
+
+    constructor(private renderer: Renderer2, private el: ElementRef) {
+
+    }
+
+    ngOnInit(): void {
+        // console.log("Init appViewPermission " + this.permission + " " + typeof (this.permission));
+        // this.togglePermission();
+    }
+
+    private togglePermission() {
+        if (this.permission) {
+            this.removeClass("d-none");
+        } else {
+            this.addClass("d-none");
+        }
+    }
+
+    addClass(className: string) {
+        // make sure you declare classname in your main style.css
+        this.renderer.addClass(this.el.nativeElement, className);
+    }
+
+    removeClass(className: string) {
+        this.renderer.removeClass(this.el.nativeElement, className);
+    }
+
+    ngOnChanges(changes: SimpleChanges): void {
+        if (changes.permission != null &&
+            (changes.permission.firstChange || changes.permission.currentValue != changes.permission.previousValue)) {
+            // console.debug("Changed " + JSON.stringify(changes));
+            this.togglePermission();
+        }
+    }
+
+}
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
index 6204371..9b3649d 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/operation.spec.ts
similarity index 77%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/operation.spec.ts
index 6204371..35849e2 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/operation.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,11 +15,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-export class AccessToken {
-    access_token: string;
-    refresh_token: string;
-    expires_in: number;
-    token_type: string;
-    scope: string;
-    state: string;
-}
+
+import { Operation } from './operation';
+
+describe('Operation', () => {
+  it('should create an instance', () => {
+    expect(new Operation()).toBeTruthy();
+  });
+});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/operation.ts
similarity index 77%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/operation.ts
index 6204371..eded8ae 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/operation.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,11 +15,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-export class AccessToken {
-    access_token: string;
-    refresh_token: string;
-    expires_in: number;
-    token_type: string;
-    scope: string;
-    state: string;
+
+export class Operation {
+    name: string;
+    description: string;
+    descriptionKey: string;
+    permanent: boolean;
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/permission.spec.ts
similarity index 77%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/permission.spec.ts
index 6204371..bf2e5af 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/permission.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,11 +15,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-export class AccessToken {
-    access_token: string;
-    refresh_token: string;
-    expires_in: number;
-    token_type: string;
-    scope: string;
-    state: string;
-}
+
+import { Permission } from './permission';
+
+describe('Permission', () => {
+  it('should create an instance', () => {
+    expect(new Permission()).toBeTruthy();
+  });
+});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/permission.ts
similarity index 72%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/permission.ts
index 6204371..53d00cf 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/permission.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,11 +15,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-export class AccessToken {
-    access_token: string;
-    refresh_token: string;
-    expires_in: number;
-    token_type: string;
-    scope: string;
-    state: string;
+
+import {Operation} from "./operation";
+import {Resource} from "./resource";
+
+export class Permission {
+    name: string;
+    description: string;
+    permanent: boolean;
+    descriptionKey: string;
+    operation: Operation;
+    resource: Resource;
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/resource.spec.ts
similarity index 77%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/resource.spec.ts
index 6204371..10ed83c 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/resource.spec.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,11 +15,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-export class AccessToken {
-    access_token: string;
-    refresh_token: string;
-    expires_in: number;
-    token_type: string;
-    scope: string;
-    state: string;
-}
+
+import { Resource } from './resource';
+
+describe('Resource', () => {
+  it('should create an instance', () => {
+    expect(new Resource()).toBeTruthy();
+  });
+});
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/resource.ts
similarity index 77%
copy from archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
copy to archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/resource.ts
index 6204371..f00f39f 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/access-token.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/resource.ts
@@ -7,8 +7,7 @@
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -16,11 +15,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-export class AccessToken {
-    access_token: string;
-    refresh_token: string;
-    expires_in: number;
-    token_type: string;
-    scope: string;
-    state: string;
+
+export class Resource {
+    identifier: string;
+    permanent: boolean;
+    pattern: boolean;
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts
index 6356fe2..0ac142b 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/user-info.ts
@@ -28,7 +28,6 @@ export class UserInfo {
     timestampAccountCreation:Date;
     timestampLastLogin:Date;
     timestampLastPasswordChange:Date;
-    assignedRoles:string[];
     readOnly:boolean;
     userManagerId:string;
     validationToken:string;
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/sidemenu/sidemenu.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/sidemenu/sidemenu.component.html
index 81b53df..c394e89 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/sidemenu/sidemenu.component.html
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/sidemenu/sidemenu.component.html
@@ -18,19 +18,22 @@
 -->
 <nav class="nav flex-column nav-pills " role="tablist" aria-orientation="vertical">
 
-  <a  class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Artifacts</a>
+  <div [appViewPermission]="perms.menu.repo.section">
+  <a  class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true" >Artifacts</a>
 
   <a  class="nav-link active my-0 py-0" href="#" data-toggle="pill"
-     role="tab" aria-controls="v-pills-search" aria-selected="true">Search</a>
+     role="tab" aria-controls="v-pills-search" aria-selected="true" >Search</a>
 
   <a  class="nav-link my-0 py-0"  href="#" data-toggle="pill"
      role="tab" aria-controls="v-pills-browse" aria-selected="false">Browse</a>
 
   <a  class="nav-link my-0 py-0" href="#" data-toggle="pill"
-     role="tab" aria-controls="v-pills-browse" aria-selected="false">Upload Artifact</a>
-
+     role="tab" aria-controls="v-pills-browse" aria-selected="false"
+    [appViewPermission]="perms.menu.repo.upload">Upload Artifact</a>
+  </div>
+  <div [appViewPermission]="perms.menu.admin.section">
   <a  class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true" data-toggle="pill"
-     role="tab" aria-controls="v-pills-home" aria-selected="false">Administration</a>
+     role="tab" aria-controls="v-pills-home" aria-selected="false" >Administration</a>
   <a  class="nav-link my-0 py-0" href="#" data-toggle="pill"
      role="tab" aria-controls="v-pills-browse" aria-selected="false">Repository Groups</a>
   <a  class="nav-link my-0 py-0" href="#" data-toggle="pill"
@@ -51,6 +54,8 @@
      role="tab" aria-controls="v-pills-browse" aria-selected="false">UI Configuration</a>
   <a   class="nav-link my-0 py-0" href="#" data-toggle="pill"
      role="tab" aria-controls="v-pills-browse" aria-selected="false">Reports</a>
+  </div>
+  <div [appViewPermission]="perms.menu.user.section">
   <a  class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true" data-toggle="pill"
      role="tab" aria-controls="v-pills-home" aria-selected="false">Users</a>
   <a   class="nav-link my-0 py-0" href="#" data-toggle="pill"
@@ -59,6 +64,7 @@
      role="tab" aria-controls="v-pills-browse" aria-selected="false">Roles</a>
   <a   class="nav-link my-0 py-0" href="#" data-toggle="pill"
      role="tab" aria-controls="v-pills-browse" aria-selected="false">Users Runtime Configuration</a>
+  </div>
   <a  class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true" data-toggle="pill"
      role="tab" aria-controls="v-pills-home" aria-selected="false">Documentation</a>
   <a   class="nav-link my-0 py-0" href="#" data-toggle="pill"
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/sidemenu/sidemenu.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/sidemenu/sidemenu.component.ts
index a02692d..c4fa16c 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/sidemenu/sidemenu.component.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/general/sidemenu/sidemenu.component.ts
@@ -17,6 +17,7 @@
  * under the License.
  */
 import { Component, OnInit } from '@angular/core';
+import {UserService} from "../../../services/user.service";
 
 @Component({
   selector: 'app-sidemenu',
@@ -25,7 +26,11 @@ import { Component, OnInit } from '@angular/core';
 })
 export class SidemenuComponent implements OnInit {
 
-  constructor() { }
+  perms;
+
+  constructor(private user: UserService) {
+    this.perms = user.uiPermissions;
+  }
 
   ngOnInit(): void {
   }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
index ba4cdb1..b4a2478 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/authentication.service.ts
@@ -33,17 +33,22 @@ import {UserInfo} from "../model/user-info";
     providedIn: 'root'
 })
 export class AuthenticationService {
-    loggedIn: boolean;
+    authenticated: boolean;
 
     /**
      * The LoginEvent is emitted, when a successful login happened. And the corresponding user info was retrieved.
      */
     public LoginEvent: EventEmitter<UserInfo> = new EventEmitter<UserInfo>();
 
+    /**
+     * The LogoutEvent is emitted, when the user has been logged out.
+     */
+    public LogoutEvent: EventEmitter<any> = new EventEmitter<any>();
+
 
     constructor(private rest: ArchivaRequestService,
                 private userService: UserService) {
-        this.loggedIn = false;
+        this.authenticated = false;
         this.restoreLoginData();
     }
 
@@ -55,12 +60,34 @@ export class AuthenticationService {
                 let expDate = new Date(expirationDate);
                 let currentDate = new Date();
                 if (currentDate < expDate) {
-                    this.loggedIn = true
                     let observer = this.userService.retrieveUserInfo();
-                    observer.subscribe(userInfo =>
-                        this.LoginEvent.emit(userInfo)
+                    observer.subscribe({
+                            next: (userInfo: UserInfo) => {
+                                if (userInfo != null) {
+                                    let permObserver = this.userService.retrievePermissionInfo();
+                                    permObserver.subscribe({
+                                            next: () => {
+                                                this.authenticated = true;
+                                                this.LoginEvent.emit(userInfo)
+                                            },
+                                            error: (err) => {
+                                                console.debug("Error retrieving perms: " + JSON.stringify(err));
+                                            }
+                                        }
+                                    )
+                                }
+                            },
+                            error: (err: HttpErrorResponse) => {
+                                console.debug("Error retrieving user info: " + JSON.stringify(err));
+                                this.logout();
+                            }
+                        }
                     );
+                } else {
+                    this.logout();
                 }
+            } else {
+                this.logout();
             }
         }
 
@@ -94,9 +121,16 @@ export class AuthenticationService {
                     localStorage.setItem("token_expire", dt.toISOString());
                 }
                 let userObserver = this.userService.retrieveUserInfo();
-                this.loggedIn = true;
-                userObserver.subscribe(userInfo =>
-                    this.LoginEvent.emit(userInfo));
+                this.authenticated = true;
+                userObserver.subscribe(userInfo => {
+                    if (userInfo != null) {
+                        let permObserver = this.userService.retrievePermissionInfo();
+                        permObserver.subscribe((perms) => {
+                                this.LoginEvent.emit(userInfo);
+                            }
+                        )
+                    }
+                });
                 resultHandler("OK");
             },
             error: (err: HttpErrorResponse) => {
@@ -104,7 +138,7 @@ export class AuthenticationService {
                 let result = err.error as ErrorResult
                 if (result.errorMessages != null) {
                     for (let msg of result.errorMessages) {
-                        console.error('Observer got an error: ' + msg.errorKey)
+                        console.debug('Observer got an error: ' + msg.errorKey)
                     }
                     resultHandler("ERROR", result.errorMessages);
                 } else {
@@ -125,8 +159,9 @@ export class AuthenticationService {
         localStorage.removeItem("access_token");
         localStorage.removeItem("refresh_token");
         localStorage.removeItem("token_expire");
-        this.loggedIn = false;
+        this.authenticated = false;
         this.userService.resetUser();
         this.rest.resetToken();
+        this.LogoutEvent.emit();
     }
 }
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
index 3360e55..a1cc652 100644
--- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
+++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/user.service.ts
@@ -16,23 +16,77 @@
  * under the License.
  */
 
-import {Injectable} from '@angular/core';
+import {Injectable, OnDestroy, OnInit} from '@angular/core';
 import {ArchivaRequestService} from "./archiva-request.service";
 import {UserInfo} from '../model/user-info';
 import {HttpErrorResponse} from "@angular/common/http";
 import {ErrorResult} from "../model/error-result";
 import {Observable} from "rxjs";
+import {Permission} from '../model/permission';
 
 @Injectable({
     providedIn: 'root'
 })
-export class UserService {
+export class UserService implements OnInit, OnDestroy {
 
     userInfo: UserInfo;
+    permissions: Permission[];
+    guestPermissions: Permission[];
+    authenticated: boolean;
+    uiPermissionsDefault  = {
+        'menu': {
+            'repo':{
+                'section':true,
+                'browse':true,
+                'search':true,
+                'upload':false
+            },
+            'admin':{
+                'section':false,
+                'config':false,
+                'status':false,
+                'reports':false
+            },
+            'user':{
+                'section':false,
+                'manage':false,
+                'roles':false,
+                'config':false
+            }
+        }
+    };
+    uiPermissions;
 
     constructor(private rest: ArchivaRequestService) {
-        this.userInfo = new UserInfo()
+        this.userInfo = new UserInfo();
+        this.uiPermissions = {};
+        this.deepCopy(this.uiPermissionsDefault, this.uiPermissions);
+    }
+
+    ngOnDestroy(): void {
+        this.resetUser();
+    }
+
+    ngOnInit(): void {
+        this.userInfo.user_id = "guest";
         this.loadPersistedUserInfo();
+        this.authenticated = false;
+        this.deepCopy(this.uiPermissionsDefault, this.uiPermissions);
+        if (this.guestPermissions == null) {
+            let observer = {
+                next: (permList: Permission[]) => {
+                    this.guestPermissions = permList;
+                    if (!this.authenticated) {
+                        this.permissions = this.guestPermissions;
+                        this.parsePermissions(this.permissions);
+                    }
+                },
+                error: err => {
+                    console.log("Could not retrieve permissions "+err);
+                }
+            }
+            this.retrievePermissionInfo().subscribe(observer);
+        }
     }
 
     /**
@@ -53,16 +107,20 @@ export class UserService {
                             this.loadPersistedUserInfo();
                         }
                         this.persistUserInfo();
+                        this.authenticated = true;
                         resultObserver.next(this.userInfo);
                     },
                     error: (err: HttpErrorResponse) => {
                         console.log("Error " + (JSON.stringify(err)));
                         let result = err.error as ErrorResult
-                        if (result.errorMessages != null) {
+                        if (result != null && result.errorMessages != null) {
                             for (let msg of result.errorMessages) {
                                 console.error('Observer got an error: ' + msg.errorKey)
                             }
+                        } else if (err.message != null) {
+                            console.error("Bad response from user info call: " + err.message);
                         }
+                        this.authenticated = false;
                         resultObserver.error();
                     },
                     complete: () => {
@@ -75,6 +133,96 @@ export class UserService {
     }
 
     /**
+     * Retrieves the permission list from the REST service
+     */
+    public retrievePermissionInfo(): Observable<Permission[]> {
+        return new Observable<Permission[]>((resultObserver) => {
+            let userName = this.authenticated ? "me" : "guest";
+            let infoObserver = this.rest.executeRestCall<Permission[]>("get", "redback", "users/" + userName + "/permissions", null);
+            let permissionObserver = {
+                next: (x: Permission[]) => {
+                    this.permissions = x;
+                    this.parsePermissions(x);
+                    resultObserver.next(this.permissions);
+                },
+                error: (err: HttpErrorResponse) => {
+                    console.log("Error " + (JSON.stringify(err)));
+                    let result = err.error as ErrorResult
+                    if (result.errorMessages != null) {
+                        for (let msg of result.errorMessages) {
+                            console.debug('Observer got an error: ' + msg.errorKey)
+                        }
+                    }
+                    this.resetPermissions();
+                    resultObserver.error(err);
+                },
+                complete: () => {
+                    resultObserver.complete();
+                }
+            };
+            infoObserver.subscribe(permissionObserver);
+
+        });
+    }
+
+    resetPermissions() {
+        this.deepCopy(this.uiPermissionsDefault, this.uiPermissions);
+    }
+    parsePermissions(permissions: Permission[]) {
+        this.resetPermissions();
+        for ( let perm of permissions) {
+            // console.debug("Checking permission for op: " + perm.operation.name);
+            switch (perm.operation.name) {
+                case "archiva-manage-configuration": {
+                    if (perm.resource.identifier=='*') {
+                        this.uiPermissions.menu.admin.section = true;
+                        this.uiPermissions.menu.admin.config = true;
+                        this.uiPermissions.menu.admin.reports = true;
+                        this.uiPermissions.menu.admin.status = true;
+                    }
+
+                }
+                case "archiva-manage-users": {
+                    if (perm.resource.identifier=='*') {
+                        this.uiPermissions.menu.user.section = true;
+                        this.uiPermissions.menu.user.config = true;
+                        this.uiPermissions.menu.user.manage = true;
+                        this.uiPermissions.menu.user.roles = true;
+                    }
+                }
+                case "redback-configuration-edit": {
+                    if (perm.resource.identifier=='*') {
+                        this.uiPermissions.menu.user.section = true;
+                        this.uiPermissions.menu.user.config = true;
+                    }
+                }
+                case "archiva-upload-file": {
+                    this.uiPermissions.menu.repo.upload = true;
+                }
+            }
+        }
+        console.log("New permissions: " + JSON.stringify(this.uiPermissions));
+    }
+
+    private deepCopy(src: Object, dst: Object) {
+        Object.keys(src).forEach((key, idx) => {
+            let srcEl = src[key];
+            if (typeof(srcEl)=='object' ) {
+                let dstEl;
+                if (!dst.hasOwnProperty(key)) {
+                    dst[key] = {}
+                }
+                dstEl = dst[key];
+                this.deepCopy(srcEl, dstEl);
+            } else {
+                // console.debug("setting " + key + " = " + srcEl);
+                dst[key] = srcEl;
+            }
+        });
+    }
+
+
+    /**
      * Stores user information persistent. Not the complete UserInfo object, only properties, that
      * are needed.
      */
@@ -104,6 +252,9 @@ export class UserService {
      */
     resetUser() {
         this.userInfo = new UserInfo();
+        this.userInfo.user_id = "guest";
+        this.resetPermissions();
+        this.authenticated = false;
     }
 
 }