浏览代码

Dynamic Groups

动态用户组
Crystal.Sea 4 年之前
父节点
当前提交
b4b0f74a9e
共有 30 个文件被更改,包括 808 次插入614 次删除
  1. 0 571
      maxkey-core/src/main/java/org/apache/commons/ssl/HostnameVerifier.java
  2. 6 0
      maxkey-core/src/main/java/org/maxkey/domain/Groups.java
  3. 38 0
      maxkey-core/src/main/java/org/maxkey/domain/Roles.java
  4. 4 0
      maxkey-core/src/main/java/org/maxkey/web/InitializeContext.java
  5. 14 11
      maxkey-core/src/main/java/org/maxkey/web/WebContext.java
  6. 10 0
      maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/GroupMemberMapper.java
  7. 3 0
      maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/GroupsMapper.java
  8. 7 0
      maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/RoleMemberMapper.java
  9. 2 0
      maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/RolesMapper.java
  10. 13 0
      maxkey-persistence/src/main/java/org/maxkey/persistence/service/GroupMemberService.java
  11. 31 0
      maxkey-persistence/src/main/java/org/maxkey/persistence/service/GroupsService.java
  12. 14 0
      maxkey-persistence/src/main/java/org/maxkey/persistence/service/RoleMemberService.java
  13. 27 0
      maxkey-persistence/src/main/java/org/maxkey/persistence/service/RolesService.java
  14. 51 2
      maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/GroupMemberMapper.xml
  15. 10 0
      maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/GroupsMapper.xml
  16. 53 4
      maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/RoleMemberMapper.xml
  17. 11 3
      maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/RolesMapper.xml
  18. 53 5
      maxkey-web-manage/src/main/java/org/maxkey/MaxKeyMgtConfig.java
  19. 56 0
      maxkey-web-manage/src/main/java/org/maxkey/jobs/DynamicGroupsJob.java
  20. 5 0
      maxkey-web-manage/src/main/java/org/maxkey/jobs/DynamicRolesJob.java
  21. 3 1
      maxkey-web-manage/src/main/java/org/maxkey/web/contorller/GroupsController.java
  22. 3 1
      maxkey-web-manage/src/main/java/org/maxkey/web/contorller/RolesController.java
  23. 3 1
      maxkey-web-manage/src/main/resources/maxkey.properties
  24. 3 0
      maxkey-web-manage/src/main/resources/messages/message.properties
  25. 3 0
      maxkey-web-manage/src/main/resources/messages/message_en.properties
  26. 3 0
      maxkey-web-manage/src/main/resources/messages/message_zh_CN.properties
  27. 6 6
      maxkey-web-manage/src/main/resources/templates/views/groups/groupsList.ftl
  28. 175 1
      maxkey-web-manage/src/main/resources/templates/views/roles/roleAdd.ftl
  29. 189 1
      maxkey-web-manage/src/main/resources/templates/views/roles/roleUpdate.ftl
  30. 12 7
      maxkey-web-manage/src/main/resources/templates/views/roles/rolesList.ftl

+ 0 - 571
maxkey-core/src/main/java/org/apache/commons/ssl/HostnameVerifier.java

@@ -1,571 +0,0 @@
-package org.apache.commons.ssl;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-/*
- * $HeadURL: http://juliusdavies.ca/svn/not-yet-commons-ssl/trunk/src/java/org/apache/commons/ssl/HostnameVerifier.java $
- * $Revision: 121 $
- * $Date: 2007-11-14 09:26:57 +0400 (Ср., 14 нояб. 2007) $
- *
- * ====================================================================
- * 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.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation.  For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-import javax.security.auth.x500.X500Principal;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.TreeSet;
-
-/**
- * Interface for checking if a hostname matches the names stored inside the
- * server's X.509 certificate.  Correctly implements
- * javax.net.ssl.HostnameVerifier, but that interface is not recommended.
- * Instead we added several check() methods that take SSLSocket,
- * or X509Certificate, or ultimately (they all end up calling this one),
- * String.  (It's easier to supply JUnit with Strings instead of mock
- * SSLSession objects!)
- * </p><p>Our check() methods throw exceptions if the name is
- * invalid, whereas javax.net.ssl.HostnameVerifier just returns true/false.
- * <p/>
- * We provide the HostnameVerifier.DEFAULT, HostnameVerifier.STRICT, and
- * HostnameVerifier.ALLOW_ALL implementations.  We also provide the more
- * specialized HostnameVerifier.DEFAULT_AND_LOCALHOST, as well as
- * HostnameVerifier.STRICT_IE6.  But feel free to define your own
- * implementations!
- * <p/>
- * Inspired by Sebastian Hauer's original StrictSSLProtocolSocketFactory in the
- * HttpClient "contrib" repository.
- *
- * @author Julius Davies
- * @author <a href="mailto:hauer@psicode.com">Sebastian Hauer</a>
- * @since 8-Dec-2006
- */
-public interface HostnameVerifier extends javax.net.ssl.HostnameVerifier {
-
-    boolean verify(String host, SSLSession session);
-
-    void check(String host, SSLSocket ssl) throws IOException;
-
-    void check(String host, X509Certificate cert) throws SSLException;
-
-    void check(String host, String[] cns, String[] subjectAlts)
-        throws SSLException;
-
-    void check(String[] hosts, SSLSocket ssl) throws IOException;
-
-    void check(String[] hosts, X509Certificate cert) throws SSLException;
-
-
-    /**
-     * Checks to see if the supplied hostname matches any of the supplied CNs
-     * or "DNS" Subject-Alts.  Most implementations only look at the first CN,
-     * and ignore any additional CNs.  Most implementations do look at all of
-     * the "DNS" Subject-Alts. The CNs or Subject-Alts may contain wildcards
-     * according to RFC 2818.
-     *
-     * @param cns         CN fields, in order, as extracted from the X.509
-     *                    certificate.
-     * @param subjectAlts Subject-Alt fields of type 2 ("DNS"), as extracted
-     *                    from the X.509 certificate.
-     * @param hosts       The array of hostnames to verify.
-     * @throws SSLException If verification failed.
-     */
-    void check(String[] hosts, String[] cns, String[] subjectAlts)
-        throws SSLException;
-
-
-    /**
-     * The DEFAULT HostnameVerifier works the same way as Curl and Firefox.
-     * <p/>
-     * The hostname must match either the first CN, or any of the subject-alts.
-     * A wildcard can occur in the CN, and in any of the subject-alts.
-     * <p/>
-     * The only difference between DEFAULT and STRICT is that a wildcard (such
-     * as "*.foo.com") with DEFAULT matches all subdomains, including
-     * "a.b.foo.com".
-     */
-    public final static HostnameVerifier DEFAULT =
-        new AbstractVerifier() {
-            public final void check(final String[] hosts, final String[] cns,
-                                    final String[] subjectAlts)
-                throws SSLException {
-                check(hosts, cns, subjectAlts, false, false);
-            }
-
-            public final String toString() { return "DEFAULT"; }
-        };
-
-
-    /**
-     * The DEFAULT_AND_LOCALHOST HostnameVerifier works like the DEFAULT
-     * one with one additional relaxation:  a host of "localhost",
-     * "localhost.localdomain", "127.0.0.1", "::1" will always pass, no matter
-     * what is in the server's certificate.
-     */
-    public final static HostnameVerifier DEFAULT_AND_LOCALHOST =
-        new AbstractVerifier() {
-            public final void check(final String[] hosts, final String[] cns,
-                                    final String[] subjectAlts)
-                throws SSLException {
-                if (isLocalhost(hosts[0])) {
-                    return;
-                }
-                check(hosts, cns, subjectAlts, false, false);
-            }
-
-            public final String toString() { return "DEFAULT_AND_LOCALHOST"; }
-        };
-
-    /**
-     * The STRICT HostnameVerifier works the same way as java.net.URL in Sun
-     * Java 1.4, Sun Java 5, Sun Java 6.  It's also pretty close to IE6.
-     * This implementation appears to be compliant with RFC 2818 for dealing
-     * with wildcards.
-     * <p/>
-     * The hostname must match either the first CN, or any of the subject-alts.
-     * A wildcard can occur in the CN, and in any of the subject-alts.  The
-     * one divergence from IE6 is how we only check the first CN.  IE6 allows
-     * a match against any of the CNs present.  We decided to follow in
-     * Sun Java 1.4's footsteps and only check the first CN.
-     * <p/>
-     * A wildcard such as "*.foo.com" matches only subdomains in the same
-     * level, for example "a.foo.com".  It does not match deeper subdomains
-     * such as "a.b.foo.com".
-     */
-    public final static HostnameVerifier STRICT =
-        new AbstractVerifier() {
-            public final void check(final String[] host, final String[] cns,
-                                    final String[] subjectAlts)
-                throws SSLException {
-                check(host, cns, subjectAlts, false, true);
-            }
-
-            public final String toString() { return "STRICT"; }
-        };
-
-    /**
-     * The STRICT_IE6 HostnameVerifier works just like the STRICT one with one
-     * minor variation:  the hostname can match against any of the CN's in the
-     * server's certificate, not just the first one.  This behaviour is
-     * identical to IE6's behaviour.
-     */
-    public final static HostnameVerifier STRICT_IE6 =
-        new AbstractVerifier() {
-            public final void check(final String[] host, final String[] cns,
-                                    final String[] subjectAlts)
-                throws SSLException {
-                check(host, cns, subjectAlts, true, true);
-            }
-
-            public final String toString() { return "STRICT_IE6"; }
-        };
-
-    /**
-     * The ALLOW_ALL HostnameVerifier essentially turns hostname verification
-     * off.  This implementation is a no-op, and never throws the SSLException.
-     */
-    public final static HostnameVerifier ALLOW_ALL =
-        new AbstractVerifier() {
-            public final void check(final String[] host, final String[] cns,
-                                    final String[] subjectAlts) {
-                // Allow everything - so never blowup.
-            }
-
-            public final String toString() { return "ALLOW_ALL"; }
-        };
-
-    abstract class AbstractVerifier implements HostnameVerifier {
-
-        /**
-         * This contains a list of 2nd-level domains that aren't allowed to
-         * have wildcards when combined with country-codes.
-         * For example: [*.co.uk].
-         * <p/>
-         * The [*.co.uk] problem is an interesting one.  Should we just hope
-         * that CA's would never foolishly allow such a certificate to happen?
-         * Looks like we're the only implementation guarding against this.
-         * Firefox, Curl, Sun Java 1.4, 5, 6 don't bother with this check.
-         */
-        private final static String[] BAD_COUNTRY_2LDS =
-            {"ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info",
-                "lg", "ne", "net", "or", "org"};
-
-        private final static String[] LOCALHOSTS = {"::1", "127.0.0.1",
-            "localhost",
-            "localhost.localdomain"};
-
-
-        static {
-            // Just in case developer forgot to manually sort the array.  :-)
-            Arrays.sort(BAD_COUNTRY_2LDS);
-            Arrays.sort(LOCALHOSTS);
-        }
-
-        protected AbstractVerifier() {}
-
-        /**
-         * The javax.net.ssl.HostnameVerifier contract.
-         *
-         * @param host    'hostname' we used to create our socket
-         * @param session SSLSession with the remote server
-         * @return true if the host matched the one in the certificate.
-         */
-        public boolean verify(String host, SSLSession session) {
-            try {
-                Certificate[] certs = session.getPeerCertificates();
-                X509Certificate x509 = (X509Certificate) certs[0];
-                check(new String[]{host}, x509);
-                return true;
-            }
-            catch (SSLException e) {
-                return false;
-            }
-        }
-
-        public void check(String host, SSLSocket ssl) throws IOException {
-            check(new String[]{host}, ssl);
-        }
-
-        public void check(String host, X509Certificate cert)
-            throws SSLException {
-            check(new String[]{host}, cert);
-        }
-
-        public void check(String host, String[] cns, String[] subjectAlts)
-            throws SSLException {
-            check(new String[]{host}, cns, subjectAlts);
-        }
-
-        public void check(String host[], SSLSocket ssl)
-            throws IOException {
-            if (host == null) {
-                throw new NullPointerException("host to verify is null");
-            }
-
-            SSLSession session = ssl.getSession();
-            if (session == null) {
-                // In our experience this only happens under IBM 1.4.x when
-                // spurious (unrelated) certificates show up in the server'
-                // chain.  Hopefully this will unearth the real problem:
-                InputStream in = ssl.getInputStream();
-                in.available();
-                /*
-                  If you're looking at the 2 lines of code above because
-                  you're running into a problem, you probably have two
-                  options:
-                    #1.  Clean up the certificate chain that your server
-                         is presenting (e.g. edit "/etc/apache2/server.crt"
-                         or wherever it is your server's certificate chain
-                         is defined).
-                                               OR
-                    #2.   Upgrade to an IBM 1.5.x or greater JVM, or switch
-                          to a non-IBM JVM.
-                */
-
-                // If ssl.getInputStream().available() didn't cause an
-                // exception, maybe at least now the session is available?
-                session = ssl.getSession();
-                if (session == null) {
-                    // If it's still null, probably a startHandshake() will
-                    // unearth the real problem.
-                    ssl.startHandshake();
-
-                    // Okay, if we still haven't managed to cause an exception,
-                    // might as well go for the NPE.  Or maybe we're okay now?
-                    session = ssl.getSession();
-                }
-            }
-            Certificate[] certs;
-            try {
-                certs = session.getPeerCertificates();
-            } catch (SSLPeerUnverifiedException spue) {
-                InputStream in = ssl.getInputStream();
-                in.available();
-                // Didn't trigger anything interesting?  Okay, just throw
-                // original.
-                throw spue;
-            }
-            X509Certificate x509 = (X509Certificate) certs[0];
-            check(host, x509);
-        }
-
-        public void check(String[] host, X509Certificate cert)
-            throws SSLException {
-            String[] cns = getCNs(cert);
-            String[] subjectAlts = getDNSSubjectAlts(cert);
-            check(host, cns, subjectAlts);
-        }
-
-        
-        public void check(final String[] hosts, final String[] cns,
-                          final String[] subjectAlts, final boolean ie6,
-                          final boolean strictWithSubDomains)
-            throws SSLException {
-            // Build up lists of allowed hosts For logging/debugging purposes.
-            StringBuffer buf = new StringBuffer(32);
-            buf.append('<');
-            for (int i = 0; i < hosts.length; i++) {
-                String h = hosts[i];
-                h = h != null ? h.trim().toLowerCase() : "";
-                hosts[i] = h;
-                if (i > 0) {
-                    buf.append('/');
-                }
-                buf.append(h);
-            }
-            buf.append('>');
-            String hostnames = buf.toString();
-            // Build the list of names we're going to check.  Our DEFAULT and
-            // STRICT implementations of the HostnameVerifier only use the
-            // first CN provided.  All other CNs are ignored.
-            // (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way).
-            TreeSet names = new TreeSet();
-            if (cns != null && cns.length > 0 && cns[0] != null) {
-                names.add(cns[0]);
-                if (ie6) {
-                    for (int i = 1; i < cns.length; i++) {
-                        names.add(cns[i]);
-                    }
-                }
-            }
-            if (subjectAlts != null) {
-                for (int i = 0; i < subjectAlts.length; i++) {
-                    if (subjectAlts[i] != null) {
-                        names.add(subjectAlts[i]);
-                    }
-                }
-            }
-            if (names.isEmpty()) {
-                String msg = "Certificate for " + hosts[0] + " doesn't contain CN or DNS subjectAlt";
-                throw new SSLException(msg);
-            }
-
-            // StringBuffer for building the error message.
-            buf = new StringBuffer();
-
-            boolean match = false;
-            out:
-            for (Iterator it = names.iterator(); it.hasNext();) {
-                // Don't trim the CN, though!
-                String cn = (String) it.next();
-                cn = cn.toLowerCase();
-                // Store CN in StringBuffer in case we need to report an error.
-                buf.append(" <");
-                buf.append(cn);
-                buf.append('>');
-                if (it.hasNext()) {
-                    buf.append(" OR");
-                }
-
-                // The CN better have at least two dots if it wants wildcard
-                // action.  It also can't be [*.co.uk] or [*.co.jp] or
-                // [*.org.uk], etc...
-                boolean doWildcard = cn.startsWith("*.") &&
-                                     cn.lastIndexOf('.') >= 0 &&
-                                     !isIP4Address(cn) &&
-                                     acceptableCountryWildcard(cn);
-
-                for (int i = 0; i < hosts.length; i++) {
-                    final String hostName = hosts[i].trim().toLowerCase();
-                    if (doWildcard) {
-                        match = hostName.endsWith(cn.substring(1));
-                        if (match && strictWithSubDomains) {
-                            // If we're in strict mode, then [*.foo.com] is not
-                            // allowed to match [a.b.foo.com]
-                            match = countDots(hostName) == countDots(cn);
-                        }
-                    } else {
-                        match = hostName.equals(cn);
-                    }
-                    if (match) {
-                        break out;
-                    }
-                }
-            }
-            if (!match) {
-                throw new SSLException("hostname in certificate didn't match: " + hostnames + " !=" + buf);
-            }
-        }
-
-        public static boolean isIP4Address(final String cn) {
-            boolean isIP4 = true;
-            String tld = cn;
-            int x = cn.lastIndexOf('.');
-            // We only bother analyzing the characters after the final dot
-            // in the name.
-            if (x >= 0 && x + 1 < cn.length()) {
-                tld = cn.substring(x + 1);
-            }
-            for (int i = 0; i < tld.length(); i++) {
-                if (!Character.isDigit(tld.charAt(0))) {
-                    isIP4 = false;
-                    break;
-                }
-            }
-            return isIP4;
-        }
-
-        public static boolean acceptableCountryWildcard(final String cn) {
-            int cnLen = cn.length();
-            if (cnLen >= 7 && cnLen <= 9) {
-                // Look for the '.' in the 3rd-last position:
-                if (cn.charAt(cnLen - 3) == '.') {
-                    // Trim off the [*.] and the [.XX].
-                    String s = cn.substring(2, cnLen - 3);
-                    // And test against the sorted array of bad 2lds:
-                    int x = Arrays.binarySearch(BAD_COUNTRY_2LDS, s);
-                    return x < 0;
-                }
-            }
-            return true;
-        }
-
-        public static boolean isLocalhost(String host) {
-            host = host != null ? host.trim().toLowerCase() : "";
-            if (host.startsWith("::1")) {
-                int x = host.lastIndexOf('%');
-                if (x >= 0) {
-                    host = host.substring(0, x);
-                }
-            }
-            int x = Arrays.binarySearch(LOCALHOSTS, host);
-            return x >= 0;
-        }
-
-        /**
-         * Counts the number of dots "." in a string.
-         *
-         * @param s string to count dots from
-         * @return number of dots
-         */
-        public static int countDots(final String s) {
-            int count = 0;
-            for (int i = 0; i < s.length(); i++) {
-                if (s.charAt(i) == '.') {
-                    count++;
-                }
-            }
-            return count;
-        }
-    }
-    
-    //from Certificate
-    public static String getCN(X509Certificate cert) {
-        String[] cns = getCNs(cert);
-        boolean foundSomeCNs = cns != null && cns.length >= 1;
-        return foundSomeCNs ? cns[0] : null;
-    }
-
-    public static String[] getCNs(X509Certificate cert) {
-        try {
-            final String subjectPrincipal = cert.getSubjectX500Principal().getName(X500Principal.RFC2253);
-            final LinkedList<String> cnList = new LinkedList<String>();
-            final LdapName subjectDN = new LdapName(subjectPrincipal);
-            for (final Rdn rds : subjectDN.getRdns()) {
-                final Attributes attributes = rds.toAttributes();
-                final Attribute cn = attributes.get("cn");
-                if (cn != null) {
-                    try {
-                        final Object value = cn.get();
-                        if (value != null) {
-                            cnList.add(value.toString());
-                        }
-                    } catch (NoSuchElementException ignore) {
-                    } catch (NamingException ignore) {
-                    }
-                }
-            }
-            if (!cnList.isEmpty()) {
-                return cnList.toArray(new String[cnList.size()]);
-            }
-        } catch (InvalidNameException ignore) {
-        }
-        return null;
-    }
-    
-    /**
-     * Extracts the array of SubjectAlt DNS names from an X509Certificate.
-     * Returns null if there aren't any.
-     * <p/>
-     * Note:  Java doesn't appear able to extract international characters
-     * from the SubjectAlts.  It can only extract international characters
-     * from the CN field.
-     * <p/>
-     * (Or maybe the version of OpenSSL I'm using to test isn't storing the
-     * international characters correctly in the SubjectAlts?).
-     *
-     * @param cert X509Certificate
-     * @return Array of SubjectALT DNS names stored in the certificate.
-     */
-    public static String[] getDNSSubjectAlts(X509Certificate cert) {
-        LinkedList subjectAltList = new LinkedList();
-        Collection c = null;
-        try {
-            c = cert.getSubjectAlternativeNames();
-        }
-        catch (CertificateParsingException cpe) {
-            // Should probably log.debug() this?
-            cpe.printStackTrace();
-        }
-        if (c != null) {
-            Iterator it = c.iterator();
-            while (it.hasNext()) {
-                List list = (List) it.next();
-                int type = ((Integer) list.get(0)).intValue();
-                // If type is 2, then we've got a dNSName
-                if (type == 2) {
-                    String s = (String) list.get(1);
-                    subjectAltList.add(s);
-                }
-            }
-        }
-        if (!subjectAltList.isEmpty()) {
-            String[] subjectAlts = new String[subjectAltList.size()];
-            subjectAltList.toArray(subjectAlts);
-            return subjectAlts;
-        } else {
-            return null;
-        }
-    }
-
-
-}

+ 6 - 0
maxkey-core/src/main/java/org/maxkey/domain/Groups.java

@@ -188,6 +188,12 @@ public class Groups extends JpaBaseDomain implements Serializable {
         builder.append(id);
         builder.append(", name=");
         builder.append(name);
+        builder.append(", dynamic=");
+        builder.append(dynamic);
+        builder.append(", filters=");
+        builder.append(filters);
+        builder.append(", orgIdsList=");
+        builder.append(orgIdsList);
         builder.append(", isdefault=");
         builder.append(isdefault);
         builder.append(", description=");

+ 38 - 0
maxkey-core/src/main/java/org/maxkey/domain/Roles.java

@@ -38,6 +38,14 @@ public class Roles extends JpaBaseDomain implements Serializable {
     @Column
     private String name;
     @Column
+    String dynamic;
+
+    @Column
+    String filters ;
+    
+    @Column
+    String orgIdsList;
+    @Column
     String status;
     @Column
     String description;
@@ -119,6 +127,30 @@ public class Roles extends JpaBaseDomain implements Serializable {
         this.modifiedDate = modifiedDate;
     }
 
+    public String getDynamic() {
+        return dynamic;
+    }
+
+    public void setDynamic(String dynamic) {
+        this.dynamic = dynamic;
+    }
+
+    public String getFilters() {
+        return filters;
+    }
+
+    public void setFilters(String filters) {
+        this.filters = filters;
+    }
+
+    public String getOrgIdsList() {
+        return orgIdsList;
+    }
+
+    public void setOrgIdsList(String orgIdsList) {
+        this.orgIdsList = orgIdsList;
+    }
+
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
@@ -126,6 +158,12 @@ public class Roles extends JpaBaseDomain implements Serializable {
         builder.append(id);
         builder.append(", name=");
         builder.append(name);
+        builder.append(", dynamic=");
+        builder.append(dynamic);
+        builder.append(", filters=");
+        builder.append(filters);
+        builder.append(", orgIdsList=");
+        builder.append(orgIdsList);
         builder.append(", status=");
         builder.append(status);
         builder.append(", description=");

+ 4 - 0
maxkey-core/src/main/java/org/maxkey/web/InitializeContext.java

@@ -62,6 +62,10 @@ public class InitializeContext extends HttpServlet {
         _logger.info("SecurityContextHolder StrategyName " + SessionSecurityContextHolderStrategy.class.getCanonicalName());
         SecurityContextHolder.setStrategyName(SessionSecurityContextHolderStrategy.class.getCanonicalName());
         
+        WebContext.applicationContext = applicationContext;
+        
+        org.apache.mybatis.jpa.util.WebContext.applicationContext = applicationContext;
+        
         // List Environment Variables
         listEnvVars();
 

+ 14 - 11
maxkey-core/src/main/java/org/maxkey/web/WebContext.java

@@ -57,6 +57,8 @@ public final class WebContext {
     
     public static Properties properties;
     
+    public static ApplicationContext applicationContext;
+    
     public static ArrayList<String> sessionAttributeNameList = new ArrayList<String>();
     
     static {
@@ -137,23 +139,24 @@ public final class WebContext {
     }
 
     /**
-     * get ApplicationContext from web ServletContext configuration.
-     * 
+     * get ApplicationContext from web  ServletContext configuration
      * @return ApplicationContext
      */
-    public static ApplicationContext getApplicationContext() {
-        return WebApplicationContextUtils.getWebApplicationContext(
-                    getSession().getServletContext());
+    public static ApplicationContext getApplicationContext(){
+        return WebApplicationContextUtils.getWebApplicationContext(getSession().getServletContext());
     }
-
+    
     /**
-     * get bean from spring configuration by bean id.
-     * 
-     * @param id String
+     * get bean from spring configuration by bean id
+     * @param id
      * @return Object
      */
-    public static Object getBean(String id) {
-        return getApplicationContext().getBean(id);
+    public static Object getBean(String id){
+        if(applicationContext==null) {
+            return getApplicationContext().getBean(id);
+        }else {
+            return applicationContext.getBean(id);
+        }
     }
 
     // below method is common HttpServlet method

+ 10 - 0
maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/GroupMemberMapper.java

@@ -24,6 +24,7 @@ import java.util.List;
 
 import org.apache.mybatis.jpa.persistence.IJpaBaseMapper;
 import org.maxkey.domain.GroupMember;
+import org.maxkey.domain.Groups;
 
 /**
  * @author Crystal.sea
@@ -36,4 +37,13 @@ public  interface GroupMemberMapper extends IJpaBaseMapper<GroupMember> {
 	public List<GroupMember> memberInGroup(GroupMember entity);
 	public List<GroupMember> memberNotInGroup(GroupMember entity);
 	public List<GroupMember> groupMemberInGroup(GroupMember entity);
+	
+	public int addDynamicGroupMember(Groups dynamicGroup);
+	
+	public int deleteDynamicGroupMember(Groups dynamicGroup);
+	
+	public int deleteByGroupId(String groupId);
+	
+	
+	
 }

+ 3 - 0
maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/GroupsMapper.java

@@ -20,6 +20,8 @@
  */
 package org.maxkey.persistence.mapper;
 
+import java.util.List;
+
 import org.apache.mybatis.jpa.persistence.IJpaBaseMapper;
 import org.maxkey.domain.Groups;
 
@@ -30,4 +32,5 @@ import org.maxkey.domain.Groups;
 
 public  interface GroupsMapper extends IJpaBaseMapper<Groups> {
 
+    public List<Groups> queryDynamicGroups(Groups groups);
 }

+ 7 - 0
maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/RoleMemberMapper.java

@@ -24,6 +24,7 @@ import java.util.List;
 
 import org.apache.mybatis.jpa.persistence.IJpaBaseMapper;
 import org.maxkey.domain.RoleMember;
+import org.maxkey.domain.Roles;
 
 /**
  * @author Crystal.sea
@@ -36,4 +37,10 @@ public  interface RoleMemberMapper extends IJpaBaseMapper<RoleMember> {
 	public List<RoleMember> memberInRole(RoleMember entity);
 	public List<RoleMember> memberNotInRole(RoleMember entity);
 	public List<RoleMember> roleMemberInRole(RoleMember entity);
+	
+    public int addDynamicRoleMember(Roles dynamicRole);
+
+    public int deleteDynamicRoleMember(Roles dynamicRole);
+
+    public int deleteByRoleId(String roleId);
 }

+ 2 - 0
maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/RolesMapper.java

@@ -38,4 +38,6 @@ public  interface RolesMapper extends IJpaBaseMapper<Roles> {
     public int logisticDeleteRolePermissions(List<RolePermissions> rolePermissionsList);
         
     public List<RolePermissions> queryRolePermissions(RolePermissions rolePermissions);
+
+    public List<Roles> queryDynamicRoles(Roles role);
 }

+ 13 - 0
maxkey-persistence/src/main/java/org/maxkey/persistence/service/GroupMemberService.java

@@ -19,6 +19,7 @@ package org.maxkey.persistence.service;
 
 import org.apache.mybatis.jpa.persistence.JpaBaseService;
 import org.maxkey.domain.GroupMember;
+import org.maxkey.domain.Groups;
 import org.maxkey.persistence.mapper.GroupMemberMapper;
 import org.springframework.stereotype.Service;
 
@@ -37,4 +38,16 @@ public class GroupMemberService  extends JpaBaseService<GroupMember>{
 		// TODO Auto-generated method stub
 		return (GroupMemberMapper)super.getMapper();
 	}
+	
+	public int addDynamicGroupMember(Groups dynamicGroup) {
+	    return getMapper().addDynamicGroupMember(dynamicGroup);
+	}
+	
+	public int deleteDynamicGroupMember(Groups dynamicGroup) {
+	    return getMapper().deleteDynamicGroupMember(dynamicGroup);
+	}
+	public int deleteByGroupId(String groupId) {
+        return getMapper().deleteByGroupId(groupId);
+    }
+	
 }

+ 31 - 0
maxkey-persistence/src/main/java/org/maxkey/persistence/service/GroupsService.java

@@ -17,14 +17,22 @@
 
 package org.maxkey.persistence.service;
 
+import java.util.List;
+
 import org.apache.mybatis.jpa.persistence.JpaBaseService;
 import org.maxkey.domain.Groups;
 import org.maxkey.persistence.mapper.GroupsMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 @Service
 public class GroupsService  extends JpaBaseService<Groups>{
 	
+    @Autowired
+    @Qualifier("groupMemberService")
+    GroupMemberService groupMemberService;
+    
 	public GroupsService() {
 		super(GroupsMapper.class);
 	}
@@ -37,4 +45,27 @@ public class GroupsService  extends JpaBaseService<Groups>{
 		// TODO Auto-generated method stub
 		return (GroupsMapper)super.getMapper();
 	}
+	
+	
+	public List<Groups> queryDynamicGroups(Groups groups){
+	    return this.getMapper().queryDynamicGroups(groups);
+	}
+	
+	public boolean deleteById(String groupId) {
+	    this.remove(groupId);
+	    groupMemberService.deleteByGroupId(groupId);
+	    return true;
+	}
+	
+	public void refreshDynamicGroups(Groups dynamicGroup){
+	    if(dynamicGroup.getDynamic().equals("1")) {
+    	    if(dynamicGroup.getOrgIdsList()!=null && !dynamicGroup.getOrgIdsList().equals("")) {
+    	        dynamicGroup.setOrgIdsList("'"+dynamicGroup.getOrgIdsList().replace(",", "','")+"'");
+    	    }
+    	    
+    	    groupMemberService.deleteDynamicGroupMember(dynamicGroup);
+    	    groupMemberService.addDynamicGroupMember(dynamicGroup);
+	    }
+    }
+	
 }

+ 14 - 0
maxkey-persistence/src/main/java/org/maxkey/persistence/service/RoleMemberService.java

@@ -19,6 +19,7 @@ package org.maxkey.persistence.service;
 
 import org.apache.mybatis.jpa.persistence.JpaBaseService;
 import org.maxkey.domain.RoleMember;
+import org.maxkey.domain.Roles;
 import org.maxkey.persistence.mapper.RoleMemberMapper;
 import org.springframework.stereotype.Service;
 
@@ -37,4 +38,17 @@ public class RoleMemberService  extends JpaBaseService<RoleMember>{
 		// TODO Auto-generated method stub
 		return (RoleMemberMapper)super.getMapper();
 	}
+	
+	
+    public int addDynamicRoleMember(Roles dynamicRole) {
+        return getMapper().addDynamicRoleMember(dynamicRole);
+    }
+
+    public int deleteDynamicRoleMember(Roles dynamicRole) {
+        return getMapper().deleteDynamicRoleMember(dynamicRole);
+    }
+
+    public int deleteByRoleId(String roleId) {
+        return getMapper().deleteByRoleId(roleId);
+    }
 }

+ 27 - 0
maxkey-persistence/src/main/java/org/maxkey/persistence/service/RolesService.java

@@ -23,11 +23,17 @@ import org.apache.mybatis.jpa.persistence.JpaBaseService;
 import org.maxkey.domain.RolePermissions;
 import org.maxkey.domain.Roles;
 import org.maxkey.persistence.mapper.RolesMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 @Service
 public class RolesService  extends JpaBaseService<Roles>{
 	
+    @Autowired
+    @Qualifier("roleMemberService")
+    RoleMemberService roleMemberService;
+    
 	public RolesService() {
 		super(RolesMapper.class);
 	}
@@ -51,4 +57,25 @@ public class RolesService  extends JpaBaseService<Roles>{
     public List<RolePermissions> queryRolePermissions(RolePermissions rolePermissions){
         return getMapper().queryRolePermissions(rolePermissions);
     }
+    
+    public List<Roles> queryDynamicRoles(Roles dynamicRole){
+        return this.getMapper().queryDynamicRoles(dynamicRole);
+    }
+    
+    public boolean deleteById(String roleId) {
+        this.remove(roleId);
+        roleMemberService.deleteByRoleId(roleId);
+        return true;
+    }
+    
+    public void refreshDynamicRoles(Roles dynamicRole){
+        if(dynamicRole.getDynamic().equals("1")) {
+            if(dynamicRole.getOrgIdsList()!=null && !dynamicRole.getOrgIdsList().equals("")) {
+                dynamicRole.setOrgIdsList("'"+dynamicRole.getOrgIdsList().replace(",", "','")+"'");
+            }
+            
+            roleMemberService.deleteDynamicRoleMember(dynamicRole);
+            roleMemberService.addDynamicRoleMember(dynamicRole);
+        }
+    }
 }

+ 51 - 2
maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/GroupMemberMapper.xml

@@ -108,7 +108,7 @@
 				<if test="groupName != null and groupName != ''">
 					AND G.NAME = #{groupName}
 				</if>
-				AND	GM.TYPE		=	'USER'
+				AND	GM.TYPE		IN(	'USER','USER-DYNAMIC')
 				AND	GM.GROUPID	=	G.ID	
 				AND	GM.MEMBERID	=	U.ID
 	</select>
@@ -167,7 +167,7 @@
 				<if test="groupName != null and groupName != ''">
 					AND G.NAME = #{groupName}
 				</if>
-					AND	GM.TYPE		=	'USER'
+					AND	GM.TYPE		IN(	'USER','USER-DYNAMIC')
 			     	AND GM.GROUPID	=	G.ID
 			)
 	</select>
@@ -195,5 +195,54 @@
 		</if>
 	</select>
 
+	<update id="addDynamicGroupMember" parameterType="Groups" >
+    	INSERT INTO MXK_GROUP_MEMBER(
+    		ID,
+    		GROUPID,
+    		MEMBERID,
+    		TYPE
+    	)
+    	SELECT 
+    		CONCAT_WS('-','UD','${id}',U.ID) ID,
+    		'${id}' GROUPID,
+    		U.ID MEMBERID,
+    		'USER-DYNAMIC' TYPE
+    	FROM MXK_USERINFO U
+		WHERE NOT EXISTS(
+				SELECT  1 FROM MXK_GROUP_MEMBER GM 
+				WHERE GM.GROUPID=#{id}
+					AND GM.MEMBERID=U.ID
+					AND GM.TYPE='USER-DYNAMIC'
+			)
+		<if test="filters != null and filters != ''">
+				${filters}
+		</if>
+		<if test="orgIdsList != null and orgIdsList != ''">
+				AND U.DEPARTMENTID IN( ${orgIdsList})
+		</if>
+    </update>
+    
+    <delete id="deleteDynamicGroupMember" parameterType="Groups" >
+    	DELETE FROM MXK_GROUP_MEMBER GM
+    	WHERE TYPE = 'USER-DYNAMIC'
+    		AND GM.GROUPID=#{id}
+    		AND NOT EXISTS(
+		    	SELECT 1
+		    	FROM MXK_USERINFO U
+		    	WHERE 1 = 1 
+		    		AND U.ID=GM.MEMBERID
+				<if test="filters != null and filters != ''">
+						${filters}
+				</if>
+				<if test="orgIdsList != null and orgIdsList != ''">
+						AND U.DEPARTMENTID IN( ${orgIdsList})
+				</if>
+			)
+    </delete>
+    
+    <delete id="deleteByGroupId" parameterType="string" >
+    	DELETE FROM MXK_GROUP_MEMBER GM
+    	WHERE  GM.GROUPID=#{value}
+    </delete>
 	
 </mapper>

+ 10 - 0
maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/GroupsMapper.xml

@@ -23,6 +23,16 @@
 		<include refid="where_statement"/>
 	</select>
 	
+	<select id="queryDynamicGroups" parameterType="Groups" resultType="Groups">
+		SELECT
+			*
+		FROM
+			`MXK_GROUPS`
+		WHERE
+			 DYNAMIC = '1'
+		<include refid="where_statement"/>
+	</select>
+	
 	
 	<update id="logisticDelete" parameterType="Groups" >
     	UPDATE `MXK_GROUPS` SET

+ 53 - 4
maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/RoleMemberMapper.xml

@@ -108,7 +108,7 @@
 				<if test="roleName != null and roleName != ''">
 					AND R.NAME = #{roleName}
 				</if>
-				AND	RM.TYPE		=	'USER'
+				AND	RM.TYPE		IN(	'USER','USER-DYNAMIC')
 				AND	RM.ROLEID	=	R.ID	
 				AND	RM.MEMBERID	=	U.ID
 	</select>
@@ -167,14 +167,14 @@
 				<if test="roleName != null and roleName != ''">
 					AND R.NAME = #{roleName}
 				</if>
-					AND	RM.TYPE		=	'USER'
+					AND	RM.TYPE		IN(	'USER','USER-DYNAMIC')
 			     	AND RM.ROLEID	=	R.ID
 			)
 	</select>
 	
 	
 	<!-- ROLE_MEMBER  Roles Member-->
-	<select id="groupMemberInRole" parameterType="RoleMember" resultType="Roles">
+	<select id="roleMemberInRole" parameterType="RoleMember" resultType="Roles">
 		SELECT	DISTINCT
 			IR.*
 		FROM
@@ -185,7 +185,7 @@
 			 1	=	1
 			AND RM.GROUPID	=	R.ID	
 			AND	RM.MEMBERID	=	IR.ID
-			AND	RM.TYPE		=	'ROLE'
+			AND	RM.TYPE		IN(	'USER','USER-DYNAMIC')
 		<if test="roleId != null and roleId != ''">
 			AND RM.ROLEID = #{roleId}
 			AND R.ID = #{roleId}
@@ -195,5 +195,54 @@
 		</if>
 	</select>
 
+	<update id="addDynamicRoleMember" parameterType="Roles" >
+    	INSERT INTO MXK_ROLE_MEMBER(
+    		ID,
+    		ROLEID,
+    		MEMBERID,
+    		TYPE
+    	)
+    	SELECT 
+    		CONCAT_WS('-','UD','${id}',U.ID) ID,
+    		'${id}' ROLEID,
+    		U.ID MEMBERID,
+    		'USER-DYNAMIC' TYPE
+    	FROM MXK_USERINFO U
+		WHERE NOT EXISTS(
+				SELECT  1 FROM MXK_ROLE_MEMBER RM 
+				WHERE RM.ROLEID=#{id}
+					AND RM.MEMBERID=U.ID
+					AND RM.TYPE='USER-DYNAMIC'
+			)
+		<if test="filters != null and filters != ''">
+				${filters}
+		</if>
+		<if test="orgIdsList != null and orgIdsList != ''">
+				AND U.DEPARTMENTID IN( ${orgIdsList})
+		</if>
+    </update>
+    
+    <delete id="deleteDynamicRoleMember" parameterType="Roles" >
+    	DELETE FROM MXK_ROLE_MEMBER RM
+    	WHERE TYPE = 'USER-DYNAMIC'
+    		AND RM.ROLEID=#{id}
+    		AND NOT EXISTS(
+		    	SELECT 1
+		    	FROM MXK_USERINFO U
+		    	WHERE 1 = 1 
+		    		AND U.ID=RM.MEMBERID
+				<if test="filters != null and filters != ''">
+						${filters}
+				</if>
+				<if test="orgIdsList != null and orgIdsList != ''">
+						AND U.DEPARTMENTID IN( ${orgIdsList})
+				</if>
+			)
+    </delete>
+    
+    <delete id="deleteByRoleId" parameterType="string" >
+    	DELETE FROM MXK_ROLE_MEMBER RM
+    	WHERE  RM.ROLEID=#{value}
+    </delete>
 	
 </mapper>

+ 11 - 3
maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/RolesMapper.xml

@@ -11,9 +11,17 @@
 		</if>
     </sql>
     
+	<select id="queryDynamicRoles" parameterType="Roles" resultType="Roles">
+		SELECT
+			*
+		FROM
+			`MXK_GROUPS`
+		WHERE
+			 DYNAMIC = '1'
+		<include refid="where_statement"/>
+	</select>
 	
-	
-	<select id="queryPageResults" parameterType="Groups" resultType="Groups">
+	<select id="queryPageResults" parameterType="Roles" resultType="Roles">
 		SELECT
 			*
 		FROM
@@ -24,7 +32,7 @@
 	</select>
 	
 	
-	<update id="logisticDelete" parameterType="Groups" >
+	<update id="logisticDelete" parameterType="Roles" >
     	UPDATE MXK_ROLES SET
     		STATUS	=	'2'  
     	WHERE	1	=	1

+ 53 - 5
maxkey-web-manage/src/main/java/org/maxkey/MaxKeyMgtConfig.java

@@ -24,10 +24,20 @@ import org.maxkey.authz.oauth2.provider.token.TokenStore;
 import org.maxkey.authz.oauth2.provider.token.store.InMemoryTokenStore;
 import org.maxkey.authz.oauth2.provider.token.store.JdbcTokenStore;
 import org.maxkey.authz.oauth2.provider.token.store.RedisTokenStore;
-import org.maxkey.authz.oidc.idtoken.OIDCIdTokenEnhancer;
 import org.maxkey.constants.ConstantsProperties;
 import org.maxkey.crypto.password.opt.impl.TimeBasedOtpAuthn;
+import org.maxkey.jobs.DynamicGroupsJob;
 import org.maxkey.persistence.redis.RedisConnectionFactory;
+import org.maxkey.persistence.service.GroupsService;
+import org.opensaml.xml.ConfigurationException;
+import org.quartz.CronScheduleBuilder;
+import org.quartz.CronTrigger;
+import org.quartz.JobBuilder;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.TriggerBuilder;
 import org.maxkey.authn.realm.jdbc.JdbcAuthenticationRealm;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,6 +47,7 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.PropertySource;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 import org.springframework.security.crypto.password.PasswordEncoder;
 
 @Configuration
@@ -44,9 +55,8 @@ import org.springframework.security.crypto.password.PasswordEncoder;
 public class MaxKeyMgtConfig  implements InitializingBean {
     private static final  Logger _logger = LoggerFactory.getLogger(MaxKeyMgtConfig.class);
     
-	
-	
-	@Bean(name = "oauth20JdbcClientDetailsService")
+
+    @Bean(name = "oauth20JdbcClientDetailsService")
     public JdbcClientDetailsService JdbcClientDetailsService(
                 DataSource dataSource,PasswordEncoder passwordReciprocal) {
 	    JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
@@ -111,9 +121,47 @@ public class MaxKeyMgtConfig  implements InitializingBean {
         return tfaOptAuthn;
     }
 	
+    /**
+     * schedulerJobsInit.
+     * @return schedulerJobsInit
+     * @throws ConfigurationException 
+     * @throws SchedulerException 
+     */
+    @Bean(name = "schedulerJobs")
+    public Scheduler schedulerJobs(
+            SchedulerFactoryBean schedulerFactoryBean,
+            GroupsService groupsService,
+            @Value("${config.job.cron.dynamicgroups}") String cronScheduleDynamicGroups
+            ) throws SchedulerException {
+       
+        Scheduler scheduler = schedulerFactoryBean.getScheduler();
+        dynamicGroupsJob(scheduler,cronScheduleDynamicGroups,groupsService);
+        
+        return scheduler;
+    }
+    
+	
+    private void dynamicGroupsJob(Scheduler scheduler ,
+                                  String cronSchedule,
+                                  GroupsService groupsService) throws SchedulerException {
+        JobDetail jobDetail = 
+                JobBuilder.newJob(DynamicGroupsJob.class) 
+                .withIdentity("DynamicGroupsJob", "DynamicGroups")
+                .build();
+        JobDataMap jobDataMap = new JobDataMap();
+        jobDataMap.put("groupsService", groupsService);
+        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronSchedule);
+        CronTrigger cronTrigger = 
+                TriggerBuilder.newTrigger()
+                .withIdentity("triggerDynamicGroups", "DynamicGroups")
+                .usingJobData(jobDataMap)
+                .withSchedule(scheduleBuilder)
+                .build();
+        scheduler.scheduleJob(jobDetail,cronTrigger);    
+    }
+	 
     @Override
     public void afterPropertiesSet() throws Exception {
-        // TODO Auto-generated method stub
         
     }
 

+ 56 - 0
maxkey-web-manage/src/main/java/org/maxkey/jobs/DynamicGroupsJob.java

@@ -0,0 +1,56 @@
+package org.maxkey.jobs;
+
+import java.util.List;
+
+import org.maxkey.domain.Groups;
+import org.maxkey.persistence.service.GroupsService;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DynamicGroupsJob  implements Job {
+    final static Logger _logger = LoggerFactory.getLogger(DynamicGroupsJob.class);
+    
+    private static  GroupsService groupsService = null;
+    
+    public static class JOBSTATUS{
+        public static int STOP = 0;
+        public static int RUNNING = 1;
+        public static int FINISHED = 2;
+    }
+    
+    private static int jobStatus = JOBSTATUS.STOP;
+
+    @Override
+    public void execute(JobExecutionContext context){
+        if(jobStatus == JOBSTATUS.RUNNING) {
+            _logger.info("DynamicGroupsJob is in running . " );
+            return;
+        }
+        
+        _logger.debug("DynamicGroupsJob is running ... " );
+        jobStatus = JOBSTATUS.RUNNING;
+        try {
+            if(groupsService == null) {
+                groupsService = (GroupsService) context.getMergedJobDataMap().get("groupsService");
+            }
+
+            List<Groups>  groupsList = groupsService.queryDynamicGroups(null);
+            for(Groups group : groupsList) {
+                _logger.debug("group " + group);
+                groupsService.refreshDynamicGroups(group);
+            }
+            Thread.sleep(10 *1000);
+            _logger.debug("DynamicGroupsJob is success  " );
+        }catch(Exception e) {
+            _logger.error("Exception " ,e);
+            jobStatus = JOBSTATUS.STOP;
+        }
+        jobStatus = JOBSTATUS.FINISHED;
+        _logger.debug("DynamicGroupsJob is finished . " );
+    }
+    
+    
+
+}

+ 5 - 0
maxkey-web-manage/src/main/java/org/maxkey/jobs/DynamicRolesJob.java

@@ -0,0 +1,5 @@
+package org.maxkey.jobs;
+
+public class DynamicRolesJob {
+
+}

+ 3 - 1
maxkey-web-manage/src/main/java/org/maxkey/web/contorller/GroupsController.java

@@ -85,6 +85,7 @@ public class GroupsController {
 		_logger.debug("-Add  :" + group);
 		
 		if (groupsService.insert(group)) {
+		    groupsService.refreshDynamicGroups(group);
 			return  new Message(WebContext.getI18nValue(ConstantsOperateMessage.INSERT_SUCCESS),MessageType.success);
 			
 		} else {
@@ -122,6 +123,7 @@ public class GroupsController {
 		_logger.debug("-update  group :" + group);
 		
 		if (groupsService.update(group)) {
+		    groupsService.refreshDynamicGroups(group);
 			return  new Message(WebContext.getI18nValue(ConstantsOperateMessage.UPDATE_SUCCESS),MessageType.success);
 			
 		} else {
@@ -136,7 +138,7 @@ public class GroupsController {
 	public Message delete(@ModelAttribute("group") Groups group) {
 		_logger.debug("-delete  group :" + group);
 		
-		if (groupsService.remove(group.getId())) {
+		if (groupsService.deleteById(group.getId())) {
 			return  new Message(WebContext.getI18nValue(ConstantsOperateMessage.DELETE_SUCCESS),MessageType.success);
 			
 		} else {

+ 3 - 1
maxkey-web-manage/src/main/java/org/maxkey/web/contorller/RolesController.java

@@ -85,6 +85,7 @@ public class RolesController {
 		_logger.debug("-Add  :" + role);
 		
 		if (rolesService.insert(role)) {
+		    rolesService.refreshDynamicRoles(role);
 			return  new Message(WebContext.getI18nValue(ConstantsOperateMessage.INSERT_SUCCESS),MessageType.success);
 			
 		} else {
@@ -122,6 +123,7 @@ public class RolesController {
 		_logger.debug("-update  role :" + role);
 		
 		if (rolesService.update(role)) {
+		    rolesService.refreshDynamicRoles(role);
 			return  new Message(WebContext.getI18nValue(ConstantsOperateMessage.UPDATE_SUCCESS),MessageType.success);
 			
 		} else {
@@ -136,7 +138,7 @@ public class RolesController {
 	public Message delete(@ModelAttribute("role") Roles role) {
 		_logger.debug("-delete  role :" + role);
 		
-		if (rolesService.remove(role.getId())) {
+		if (rolesService.deleteById(role.getId())) {
 			return  new Message(WebContext.getI18nValue(ConstantsOperateMessage.DELETE_SUCCESS),MessageType.success);
 			
 		} else {

+ 3 - 1
maxkey-web-manage/src/main/resources/maxkey.properties

@@ -66,4 +66,6 @@ config.oidc.metadata.issuer=https://${config.server.domain}/maxkey
 config.oidc.metadata.authorizationEndpoint=${config.server.name}/maxkey/oauth/v20/authorize
 config.oidc.metadata.tokenEndpoint=${config.server.name}/maxkey/oauth/v20/token
 config.oidc.metadata.userinfoEndpoint=${config.server.name}/maxkey/api/connect/userinfo
-#############################################################################
+#############################################################################
+#one hour for refresh dynamic groups
+config.job.cron.dynamicgroups=0 0 0/1 * * ?

+ 3 - 0
maxkey-web-manage/src/main/resources/messages/message.properties

@@ -420,6 +420,9 @@ group.orgidslist=\u673a\u6784\u5217\u8868
 #role
 role.id=\u89d2\u8272\u7f16\u7801
 role.name=\u89d2\u8272
+role.dynamic=\u52a8\u6001\u7ec4
+role.filters=\u8fc7\u6ee4\u5668
+role.orgidslist=\u673a\u6784\u5217\u8868
 
 resource.id=\u8d44\u6e90\u7f16\u7801
 resource.name=\u8d44\u6e90\u540d\u79f0

+ 3 - 0
maxkey-web-manage/src/main/resources/messages/message_en.properties

@@ -419,6 +419,9 @@ group.orgidslist=orgIdsList
 #role
 role.id=id
 role.name=name
+role.dynamic=dynamic
+role.filters=filters
+role.orgidslist=orgIdsList
 
 resource.id=id
 resource.name=name

+ 3 - 0
maxkey-web-manage/src/main/resources/messages/message_zh_CN.properties

@@ -421,6 +421,9 @@ group.orgidslist=\u673a\u6784\u5217\u8868
 #role
 role.id=\u89d2\u8272\u7f16\u7801
 role.name=\u89d2\u8272
+role.dynamic=\u52a8\u6001\u7ec4
+role.filters=\u8fc7\u6ee4\u5668
+role.orgidslist=\u673a\u6784\u5217\u8868
 
 resource.id=\u8d44\u6e90\u7f16\u7801
 resource.name=\u8d44\u6e90\u540d\u79f0

+ 6 - 6
maxkey-web-manage/src/main/resources/templates/views/groups/groupsList.ftl

@@ -3,13 +3,13 @@
 <head>
 	<#include  "../layout/header.ftl"/>
 	<#include  "../layout/common.cssjs.ftl"/>
-
+	<script type="text/javascript">	
+		function dynamicFormatter(value, row, index){
+	  		return value=='0'? '<@locale code="common.text.no" />':'<@locale code="common.text.yes" />';
+		};
+	</script>
 </head>
-<script type="text/javascript">	
-	function dynamicFormatter(value, row, index){
-  		return value=='0'? '<@locale code="common.text.no" />':'<@locale code="common.text.yes" />';
-	};
-</script>
+
 <body> 
 <div class="app header-default side-nav-dark">
 <div class="layout">

+ 175 - 1
maxkey-web-manage/src/main/resources/templates/views/roles/roleAdd.ftl

@@ -9,6 +9,155 @@
     vertical-align: middle;
   }
 </style>
+<script type="text/javascript">
+function onClick (event, treeId, treeNode) {
+	var zTree = $.fn.zTree.getZTreeObj("orgsTree");
+	nodes = zTree.getCheckedNodes(true);
+	var orgsName = "";
+	var orgsId = "";
+	for (var i=0, l=nodes.length; i<l; i++) {
+		orgsName += nodes[i].name + ",";
+		orgsId += nodes[i].id + ",";
+	} 
+	
+	$("#orgIdsListName").val(orgsName);
+	$("#orgIdsList").val(orgsId);
+}
+
+$(function () {
+
+		var treeSettings={
+			element  :  "orgsTree",
+			rootId  :  "1",
+		 	checkbox  :  true,
+		 	onClick  :  onClick,
+		 	onDblClick  :  null,
+		 	url  :  "<@base/>/orgs/tree"
+		};
+			
+		function singlePath(newNode) {
+			if (newNode === curExpandNode) return;
+			if (curExpandNode && curExpandNode.open==true) {
+				var zTree = $.fn.zTree.getZTreeObj(treeSettings.element);
+				if (newNode.parentTId === curExpandNode.parentTId) {
+					zTree.expandNode(curExpandNode, false);
+				} else {
+					var newParents = [];
+					while (newNode) {
+						newNode = newNode.getParentNode();
+						if (newNode === curExpandNode) {
+							newParents = null;
+							break;
+						} else if (newNode) {
+							newParents.push(newNode);
+						}
+					}
+					if (newParents!=null) {
+						var oldNode = curExpandNode;
+						var oldParents = [];
+						while (oldNode) {
+							oldNode = oldNode.getParentNode();
+							if (oldNode) {
+								oldParents.push(oldNode);
+							}
+						}
+						if (newParents.length>0) {
+							for (var i = Math.min(newParents.length, oldParents.length)-1; i>=0; i--) {
+								if (newParents[i] !== oldParents[i]) {
+									zTree.expandNode(oldParents[i], false);
+									break;
+								}
+							}
+						} else {
+							zTree.expandNode(oldParents[oldParents.length-1], false);
+						}
+					}
+				}
+			}
+			curExpandNode = newNode;
+		};
+
+
+		function beforeExpand(treeId, treeNode) {
+			var pNode = curExpandNode ? curExpandNode.getParentNode():null;
+			var treeNodeP = treeNode.parentTId ? treeNode.getParentNode():null;
+			var zTree = $.fn.zTree.getZTreeObj(""+treeSettings.element);
+			for(var i=0, l=!treeNodeP ? 0:treeNodeP.children.length; i<l; i++ ) {
+				if (treeNode !== treeNodeP.children[i]) {
+					zTree.expandNode(treeNodeP.children[i], false);
+				}
+			}
+			while (pNode) {
+				if (pNode === treeNode) {
+					break;
+				}
+				pNode = pNode.getParentNode();
+			}
+			if (!pNode) {
+				singlePath(treeNode);
+			}
+
+		};
+		
+	    $.fn.zTree.init(
+	    		$("#"+treeSettings.element), //element
+	    		{//json object 
+					check	: 	{
+						enable		: 	treeSettings.checkbox
+					},
+					async	: 	{
+						enable		: 	true,
+						url			:	treeSettings.url,
+						autoParam	:	["id", "name=n", "level=lv"],
+						otherParam	:	{"otherParam":"zTreeAsyncTest",id:treeSettings.rootId},
+						dataFilter	: 	function (treeId, parentNode, childNodes) {
+											if (!childNodes) return null;
+											for (var i=0, l=childNodes.length; i<l; i++) {
+												childNodes[i].name = childNodes[i].name.replace(/\.n/g, '.');
+											}
+											return childNodes;
+										}
+					},
+					data			: 	{
+						simpleData	: 	{
+							enable	: 	true
+						}
+					},
+					callback: {
+						onClick			: 	treeSettings.onClick,
+						onDblClick		: 	treeSettings.onDblClick,
+						beforeAsync		: 	function(treeId, treeNode){
+							$.loading();
+						},
+						onAsyncSuccess	: 	function(event, treeId, treeNode, msg){
+							$.unloading();
+						},
+						//beforeExpand	: 	beforeExpand,
+						onExpand		: 	function onExpand(event, treeId, treeNode) {
+							curExpandNode = treeNode;
+						}
+					}
+	    		}
+	    	);//end tree
+	
+});
+function onBodyDown(event) {
+	if (!(event.target.id == "menuBtn" || event.target.id == "orgIdsListName" || event.target.id == "orgContent" || $(event.target).parents("#orgContent").length>0)) {
+		$("#orgContent").fadeOut("fast");
+		$("body").unbind("mousedown", onBodyDown);
+	}
+}
+		
+function showOrgsTree() {
+	var treeObj = $("#orgIdsListName");
+	var treeOffset = $("#orgIdsListName").offset();
+	$("#orgContent").css({left:treeOffset.left + "px", top:treeOffset.top + treeObj.outerHeight() + "px"}).slideDown("fast");
+
+	$("body").bind("mousedown", onBodyDown);
+}
+
+
+</script>
 </head>
 <body>
 <form id="actionForm"  method="post" type="label" autoclose="true"  action="<@base/>/roles/add"  class="needs-validation" novalidate>
@@ -27,9 +176,31 @@
 				</td>
 			</tr>
 			<tr>
+				<th><@locale code="role.dynamic" />:</th>
+				<td nowrap>
+					<select id="dynamic" name="dynamic"  class="form-control">
+						<option value="0" selected ><@locale code="common.text.no" /></option>
+						<option value="1"          ><@locale code="common.text.yes" /></option>
+					</select>
+				</td>
+			</tr>
+			<tr>
+				<th><@locale code="role.orgidslist" />:</th>
+				<td nowrap>
+					<input type="text" id="orgIdsListName" name="orgIdsListName"   readonly  class="form-control" title="" value=""   onclick="showOrgsTree();"/>
+					<input type="hidden" id="orgIdsList" name="orgIdsList"   readonly  class="form-control" title="" value=""   />
+				</td>
+			</tr>
+			<tr>
+				<th><@locale code="role.filters" />:</th>
+				<td nowrap>
+					<textarea id="filters" name="filters" class="form-control"  rows="7" cols="20"></textarea>
+				</td>
+			</tr>
+			<tr>
                 <th><@locale code="common.text.description" />:</th>
                 <td nowrap>
-                    <input type="text" id="description" name="description" class="form-control" title="" value=""  />
+                    <textarea id="description" name="description" class="form-control"  rows="6" cols="20"></textarea>
                 </td>
             </tr>
 			
@@ -44,5 +215,8 @@
 		</tbody>
 	</table>
 </form>
+<div id="orgContent" class="menuContent" style="display:none; position: absolute;">
+	<ul id="orgsTree" class="ztree" style="margin-top:0; width:180px; height: 300px;"></ul>
+</div>
 </body>
 </html>

+ 189 - 1
maxkey-web-manage/src/main/resources/templates/views/roles/roleUpdate.ftl

@@ -9,6 +9,169 @@
     vertical-align: middle;
   }
 </style>
+<script type="text/javascript">
+function onClick (event, treeId, treeNode) {
+	var zTree = $.fn.zTree.getZTreeObj("orgsTree");
+	nodes = zTree.getCheckedNodes(true);
+	var orgsName = "";
+	var orgsId = "";
+	for (var i=0; i<nodes.length; i++) {
+		orgsName += nodes[i].name + ",";
+		orgsId += nodes[i].id + ",";
+	} 
+	
+	$("#orgIdsListName").val(orgsName);
+	$("#orgIdsList").val(orgsId);
+}
+
+$(function () {
+
+	var treeSettings={
+		element  :  "orgsTree",
+		rootId  :  "1",
+	 	checkbox  :  true,
+	 	onClick  :  onClick,
+	 	onDblClick  :  null,
+	 	url  :  "<@base/>/orgs/tree"
+	};
+		
+	function singlePath(newNode) {
+		if (newNode === curExpandNode) return;
+		if (curExpandNode && curExpandNode.open==true) {
+			var zTree = $.fn.zTree.getZTreeObj(treeSettings.element);
+			if (newNode.parentTId === curExpandNode.parentTId) {
+				zTree.expandNode(curExpandNode, false);
+			} else {
+				var newParents = [];
+				while (newNode) {
+					newNode = newNode.getParentNode();
+					if (newNode === curExpandNode) {
+						newParents = null;
+						break;
+					} else if (newNode) {
+						newParents.push(newNode);
+					}
+				}
+				if (newParents!=null) {
+					var oldNode = curExpandNode;
+					var oldParents = [];
+					while (oldNode) {
+						oldNode = oldNode.getParentNode();
+						if (oldNode) {
+							oldParents.push(oldNode);
+						}
+					}
+					if (newParents.length>0) {
+						for (var i = Math.min(newParents.length, oldParents.length)-1; i>=0; i--) {
+							if (newParents[i] !== oldParents[i]) {
+								zTree.expandNode(oldParents[i], false);
+								break;
+							}
+						}
+					} else {
+						zTree.expandNode(oldParents[oldParents.length-1], false);
+					}
+				}
+			}
+		}
+		curExpandNode = newNode;
+	};
+	
+	
+	function beforeExpand(treeId, treeNode) {
+		var pNode = curExpandNode ? curExpandNode.getParentNode():null;
+		var treeNodeP = treeNode.parentTId ? treeNode.getParentNode():null;
+		var zTree = $.fn.zTree.getZTreeObj(""+treeSettings.element);
+		for(var i=0, l=!treeNodeP ? 0:treeNodeP.children.length; i<l; i++ ) {
+			if (treeNode !== treeNodeP.children[i]) {
+				zTree.expandNode(treeNodeP.children[i], false);
+			}
+		}
+		while (pNode) {
+			if (pNode === treeNode) {
+				break;
+			}
+			pNode = pNode.getParentNode();
+		}
+		if (!pNode) {
+			singlePath(treeNode);
+		}
+	
+	};
+	function onLoadSuccessed(){
+		var zTree = $.fn.zTree.getZTreeObj("orgsTree");
+		var orgsIdValues = $("#orgIdsList").val().split(",") ;
+		var orgsName="";
+		for (var i=0; i<orgsIdValues.length; i++) {
+			var node = zTree.getNodeByParam("id",orgsIdValues[i] );
+			if(node != null){
+				zTree.checkNode(node, true, false);//将指定ID的节点选中
+				orgsName +=  node.name;
+			}
+		} 
+		$("#orgIdsListName").val(orgsName);
+	}
+	
+	$.fn.zTree.init(
+		$("#"+treeSettings.element), //element
+		{//json object 
+			check	: 	{
+				enable		: 	treeSettings.checkbox
+			},
+			async	: 	{
+				enable		: 	true,
+				url			:	treeSettings.url,
+				autoParam	:	["id", "name=n", "level=lv"],
+				otherParam	:	{"otherParam":"zTreeAsyncTest",id:treeSettings.rootId},
+				dataFilter	: 	function (treeId, parentNode, childNodes) {
+									if (!childNodes) return null;
+									for (var i=0, l=childNodes.length; i<l; i++) {
+										childNodes[i].name = childNodes[i].name.replace(/\.n/g, '.');
+									}
+									return childNodes;
+								}
+			},
+			data			: 	{
+				simpleData	: 	{
+					enable	: 	true
+				}
+			},
+			callback: {
+				onClick			: 	treeSettings.onClick,
+				onDblClick		: 	treeSettings.onDblClick,
+				beforeAsync		: 	function(treeId, treeNode){
+					$.loading();
+				},
+				onAsyncSuccess	: 	function(event, treeId, treeNode, msg){
+					$.unloading();
+					onLoadSuccessed();
+				},
+				//beforeExpand	: 	beforeExpand,
+				onExpand		: 	function onExpand(event, treeId, treeNode) {
+					curExpandNode = treeNode;
+				}
+			}
+		}
+	);//end tree
+	
+});
+function onBodyDown(event) {
+	if (!(event.target.id == "menuBtn" || event.target.id == "orgIdsListName" || event.target.id == "orgContent" || $(event.target).parents("#orgContent").length>0)) {
+		$("#orgContent").fadeOut("fast");
+		$("body").unbind("mousedown", onBodyDown);
+	}
+}
+		
+function showOrgsTree() {
+	var treeObj = $("#orgIdsListName");
+	var treeOffset = $("#orgIdsListName").offset();
+	$("#orgContent").css({left:treeOffset.left + "px", top:treeOffset.top + treeObj.outerHeight() + "px"}).slideDown("fast");
+
+	$("body").bind("mousedown", onBodyDown);
+}
+
+
+</script>
 </head>
 <body>
 <form id="actionForm"  method="post" type="label" autoclose="true"  action="<@base/>/roles/update"  class="needs-validation" novalidate>
@@ -27,9 +190,31 @@
 			</td>
 		</tr>
 		<tr>
+			<th><@locale code="role.dynamic" />:</th>
+			<td nowrap>
+				<select id="dynamic" name="dynamic"  class="form-control">
+					<option value="0" <#if '0'==model.dynamic>selected</#if> ><@locale code="common.text.no" /></option>
+					<option value="1" <#if '1'==model.dynamic>selected</#if> ><@locale code="common.text.yes" /></option>
+				</select>
+			</td>
+		</tr>
+		<tr>
+				<th><@locale code="role.orgidslist" />:</th>
+				<td nowrap>
+					<input type="text" id="orgIdsListName" name="orgIdsListName"   readonly  class="form-control" title="" value=""   onclick="showOrgsTree();"/>
+					<input type="hidden" id="orgIdsList" name="orgIdsList"   readonly  class="form-control" title="" value="${model.orgIdsList!}"   />
+				</td>
+		</tr>
+		<tr>
+			<th><@locale code="role.filters" />:</th>
+			<td nowrap>
+				<textarea id="filters" name="filters" class="form-control"  rows="7" cols="20">${model.filters!}</textarea>
+			</td>
+		</tr>
+		<tr>
                 <th><@locale code="common.text.description" />:</th>
                 <td nowrap>
-                    <input type="text" id="description" name="description" class="form-control" title="" value="${model.description!}"  />
+                	<textarea id="description" name="description" class="form-control"  rows="6" cols="20">${model.description!}</textarea>
                 </td>
             </tr>
 		<tr>
@@ -43,5 +228,8 @@
 		</tbody>
 	  </table>
 </form>
+<div id="orgContent" class="menuContent" style="display:none; position: absolute;">
+	<ul id="orgsTree" class="ztree" style="margin-top:0; width:180px; height: 300px;"></ul>
+</div>
 </body>
 </html>

+ 12 - 7
maxkey-web-manage/src/main/resources/templates/views/roles/rolesList.ftl

@@ -3,7 +3,11 @@
 <head>
 	<#include  "../layout/header.ftl"/>
 	<#include  "../layout/common.cssjs.ftl"/>
-
+	<script type="text/javascript">	
+		function dynamicFormatter(value, row, index){
+	  		return value=='0'? '<@locale code="common.text.no" />':'<@locale code="common.text.yes" />';
+		};
+	</script>
 </head>
 <body> 
 <div class="app header-default side-nav-dark">
@@ -52,13 +56,13 @@
 		 					 <input class="button btn btn-success mr-3" id="addBtn" type="button" value="<@locale code="button.text.add"/>" 
 						 		    wurl="<@base/>/roles/forwardAdd"
 						 		    wwidth="500"
-						 		    wheight="200"
+						 		    wheight="600"
 					 		    	target="window">	    	
 					 		    	
 					 	<input class="button btn btn-info mr-3 " id="modifyBtn" type="button" value="<@locale code="button.text.edit"/>" 
 					 				wurl="<@base/>/roles/forwardUpdate"
 					 				wwidth="500"
-						 		    wheight="200"
+						 		    wheight="600"
 					 		    	target="window"> 
 					 		    	
 					 	<input class="button btn btn-danger mr-3 "  id="deleteBtn" type="button" value="<@locale code="button.text.delete"/>"
@@ -94,11 +98,12 @@
 				<th data-checkbox="true"></th>
 				<th data-sortable="true" data-field="id"   data-visible="false">Id</th>
 				<th data-field="name"><@locale code="role.name"/></th>
+				<th data-field="dynamic"  data-formatter="dynamicFormatter"><@locale code="group.dynamic"/></th>
 				<th data-field="description"><@locale code="common.text.description"/></th>
-				<th data-field="createdBy"><@locale code="common.text.createdby"/></th>
-				<th data-field="createdDate"><@locale code="common.text.createddate"/></th>
-				<th data-field="modifiedBy"><@locale code="common.text.modifiedby"/></th>
-				<th data-field="modifiedDate"><@locale code="common.text.modifieddate"/></th>
+				<th data-field="createdBy"    data-visible="false"><@locale code="common.text.createdby"/></th>
+				<th data-field="createdDate"  data-visible="false"><@locale code="common.text.createddate"/></th>
+				<th data-field="modifiedBy"   data-visible="false"><@locale code="common.text.modifiedby"/></th>
+				<th data-field="modifiedDate" data-visible="false"><@locale code="common.text.modifieddate"/></th>
 	
 			</tr>
 		</thead>