KB66 Add editor role.

Change-Id: Ibcf94b6532ccb1602bf169ffb434b75557767598
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/accesscontrol/Role.java b/src/main/java/eu/mulk/mulkcms2/benki/accesscontrol/Role.java
index 87b477d..1d66939 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/accesscontrol/Role.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/accesscontrol/Role.java
@@ -22,6 +22,8 @@
 import javax.persistence.OneToMany;
 import javax.persistence.OneToOne;
 import javax.persistence.Table;
+import org.hibernate.annotations.LazyToOne;
+import org.hibernate.annotations.LazyToOneOption;
 
 @Entity
 @Table(name = "roles", schema = "benki")
@@ -68,6 +70,7 @@
   public Collection<UserRole> directUsers;
 
   @OneToOne(mappedBy = "ownedRole", fetch = FetchType.LAZY)
+  @LazyToOne(LazyToOneOption.NO_PROXY)
   public User owningUsers;
 
   @ManyToMany(mappedBy = "effectiveRoles", fetch = FetchType.LAZY)
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/login/LoginRoles.java b/src/main/java/eu/mulk/mulkcms2/benki/login/LoginRoles.java
new file mode 100644
index 0000000..27c6f5c
--- /dev/null
+++ b/src/main/java/eu/mulk/mulkcms2/benki/login/LoginRoles.java
@@ -0,0 +1,8 @@
+package eu.mulk.mulkcms2.benki.login;
+
+public final class LoginRoles {
+
+  public static final String EDITOR = "EDITOR";
+
+  private LoginRoles() {}
+}
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/login/LoginStatus.java b/src/main/java/eu/mulk/mulkcms2/benki/login/LoginStatus.java
index 06a184c..a217dba 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/login/LoginStatus.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/login/LoginStatus.java
@@ -17,6 +17,10 @@
     return !identity.isAnonymous();
   }
 
+  public boolean isEditor() {
+    return identity.hasRole(LoginRoles.EDITOR);
+  }
+
   public String getUserName() {
     return identity.getPrincipal().getName();
   }
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/login/RoleAugmentor.java b/src/main/java/eu/mulk/mulkcms2/benki/login/RoleAugmentor.java
new file mode 100644
index 0000000..3aafc0e
--- /dev/null
+++ b/src/main/java/eu/mulk/mulkcms2/benki/login/RoleAugmentor.java
@@ -0,0 +1,59 @@
+package eu.mulk.mulkcms2.benki.login;
+
+import eu.mulk.mulkcms2.benki.accesscontrol.Role;
+import eu.mulk.mulkcms2.benki.users.User;
+import io.quarkus.cache.CacheResult;
+import io.quarkus.security.identity.AuthenticationRequestContext;
+import io.quarkus.security.identity.SecurityIdentity;
+import io.quarkus.security.identity.SecurityIdentityAugmentor;
+import io.quarkus.security.runtime.QuarkusSecurityIdentity;
+import io.smallrye.mutiny.Uni;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.enterprise.context.ApplicationScoped;
+import javax.transaction.Transactional;
+
+@ApplicationScoped
+public class RoleAugmentor implements SecurityIdentityAugmentor {
+
+  private static final String EDITOR_TAG = "editor";
+
+  @Override
+  public Uni<SecurityIdentity> augment(
+      SecurityIdentity identity, AuthenticationRequestContext context) {
+
+    if (identity.isAnonymous()) {
+      return Uni.createFrom().item(identity);
+    }
+
+    return augmentWithRoles(identity, context);
+  }
+
+  @Transactional
+  Uni<SecurityIdentity> augmentWithRoles(
+      SecurityIdentity identity, AuthenticationRequestContext context) {
+    return context.runBlocking(
+        () -> {
+          Set<String> loginRoles = getUserLoginRoles(identity.getPrincipal().getName());
+          return QuarkusSecurityIdentity.builder(identity).addRoles(loginRoles).build();
+        });
+  }
+
+  @CacheResult(cacheName = "login-role-cache")
+  Set<String> getUserLoginRoles(String userNickname) {
+    var user = User.findByNicknameWithRoles(userNickname);
+    return user.effectiveRoles.stream()
+        .flatMap(RoleAugmentor::roleTags)
+        .flatMap(RoleAugmentor::loginRoleOfTag)
+        .collect(Collectors.toSet());
+  }
+
+  private static Stream<String> roleTags(Role role) {
+    return role.tags.stream();
+  }
+
+  private static Stream<String> loginRoleOfTag(String tag) {
+    return tag.equals(EDITOR_TAG) ? Stream.of(LoginRoles.EDITOR) : Stream.empty();
+  }
+}
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java
index c0d8647..495c780 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/posts/PostResource.java
@@ -14,6 +14,8 @@
 import com.rometools.rome.io.WireFeedOutput;
 import eu.mulk.mulkcms2.benki.accesscontrol.PageKey;
 import eu.mulk.mulkcms2.benki.accesscontrol.Role;
+import eu.mulk.mulkcms2.benki.login.LoginRoles;
+import eu.mulk.mulkcms2.benki.login.LoginStatus;
 import eu.mulk.mulkcms2.benki.posts.Post.PostPage;
 import eu.mulk.mulkcms2.benki.users.User;
 import io.quarkus.qute.Template;
@@ -419,7 +421,7 @@
     switch (postFilter) {
       case ALL:
       case BOOKMARKS_ONLY:
-        return !identity.isAnonymous();
+        return identity.hasRole(LoginRoles.EDITOR);
       case LAZYCHAT_MESSAGES_ONLY:
         return false;
       default:
@@ -431,7 +433,7 @@
     switch (postFilter) {
       case ALL:
       case LAZYCHAT_MESSAGES_ONLY:
-        return !identity.isAnonymous();
+        return identity.hasRole(LoginRoles.EDITOR);
       case BOOKMARKS_ONLY:
         return false;
       default:
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/users/User.java b/src/main/java/eu/mulk/mulkcms2/benki/users/User.java
index 96feafc..04a5cd4 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/users/User.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/users/User.java
@@ -150,6 +150,18 @@
     return User.find("from BenkiUser u join u.nicknames n where ?1 = n", nickname).singleResult();
   }
 
+  public static User findByNicknameWithRoles(String nickname) {
+    return User.find(
+            ""
+                + "from BenkiUser u "
+                + "join u.nicknames n "
+                + "left join fetch u.effectiveRoles r "
+                + "left join fetch r.tags "
+                + "where ?1 = n",
+            nickname)
+        .singleResult();
+  }
+
   public final boolean canSee(Post message) {
     return message.isVisibleTo(this);
   }
diff --git a/src/main/resources/db/changeLog-1.8.xml b/src/main/resources/db/changeLog-1.8.xml
index 2359001..05948b0 100644
--- a/src/main/resources/db/changeLog-1.8.xml
+++ b/src/main/resources/db/changeLog-1.8.xml
@@ -55,4 +55,11 @@
     </createIndex>
   </changeSet>
 
+  <changeSet id="1.8-4" author="mulk">
+    <sql>
+      ALTER TABLE benki.role_tags DROP CONSTRAINT role_tags_tag_check;
+      ALTER TABLE benki.role_tags ADD CONSTRAINT role_tags_tag_check CHECK (tag = ANY(ARRAY['admin', 'everyone', 'world', 'editor']));
+    </sql>
+  </changeSet>
+
 </databaseChangeLog>
diff --git a/src/main/resources/templates/tags/navbar.html b/src/main/resources/templates/tags/navbar.html
index 17a25dd..750d3e1 100644
--- a/src/main/resources/templates/tags/navbar.html
+++ b/src/main/resources/templates/tags/navbar.html
@@ -10,7 +10,7 @@
     </ol>
   </li>
 
-  {#if inject:LoginStatus.loggedIn}
+  {#if inject:LoginStatus.isEditor}
   <li class='{#if siteSection == "Wiki"}this-page{/}' data-site-section="Wiki"><a href="/wiki/Home">Wiki</a></li>
   {/if}