View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.access.mcrimpl;
20  
21  import java.net.UnknownHostException;
22  import java.util.Collection;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.Hashtable;
26  
27  import org.apache.logging.log4j.LogManager;
28  import org.apache.logging.log4j.Logger;
29  import org.jdom2.Attribute;
30  import org.jdom2.Element;
31  import org.mycore.access.MCRAccessBaseImpl;
32  import org.mycore.access.MCRRuleAccessInterface;
33  import org.mycore.common.MCRException;
34  import org.mycore.common.MCRSession;
35  import org.mycore.common.MCRSessionMgr;
36  import org.mycore.common.MCRSystemUserInformation;
37  import org.mycore.common.MCRUserInformation;
38  import org.mycore.common.config.MCRConfiguration2;
39  
40  /**
41   * MyCoRe-Standard Implementation of the MCRAccessInterface Maps object ids to rules
42   *
43   * @author Matthias Kramm
44   * @author Heiko Helmbrecht
45   */
46  public class MCRAccessControlSystem extends MCRAccessBaseImpl {
47  
48      public static final String SYSTEM_RULE_PREFIX = "SYSTEMRULE";
49  
50      public static final String POOL_PRIVILEGE_ID = "POOLPRIVILEGE";
51  
52      public static final String LEXICOGRAPHICAL_PATTERN = "0000000000";
53  
54      MCRAccessStore accessStore;
55  
56      MCRRuleStore ruleStore;
57  
58      MCRAccessRule dummyRule;
59  
60      boolean disabled = false;
61  
62      static Hashtable<String, String> ruleIDTable = new Hashtable<>();
63  
64      private static final Logger LOGGER = LogManager.getLogger(MCRAccessControlSystem.class);
65  
66      private MCRAccessControlSystem() {
67          String pools = MCRConfiguration2.getString("MCR.Access.AccessPermissions").orElse("read,write,delete");
68  
69          if (pools.trim().length() == 0) {
70              disabled = true;
71          }
72  
73          accessStore = MCRAccessStore.getInstance();
74          ruleStore = MCRRuleStore.getInstance();
75  
76          nextFreeRuleID = new HashMap<>();
77  
78          dummyRule = new MCRAccessRule(null, null, null, null, "dummy rule, always true");
79      }
80  
81      private static MCRAccessControlSystem singleton;
82  
83      private static HashMap<String, Integer> nextFreeRuleID;
84  
85      // extended methods
86      public static synchronized MCRRuleAccessInterface instance() {
87          if (singleton == null) {
88              singleton = new MCRAccessControlSystem();
89          }
90          return singleton;
91      }
92  
93      @Override
94      public void createRule(String ruleString, String creator, String description) {
95          String ruleID = getNextFreeRuleID(SYSTEM_RULE_PREFIX);
96          MCRAccessRule accessRule = new MCRAccessRule(ruleID, creator, new Date(), ruleString, description);
97          ruleStore.createRule(accessRule);
98      }
99  
100     @Override
101     public void createRule(Element rule, String creator, String description) {
102         createRule(getNormalizedRuleString(rule), creator, description);
103     }
104 
105     @Override
106     public void addRule(String id, String pool, Element rule, String description) throws MCRException {
107         MCRRuleMapping ruleMapping = getAutoGeneratedRuleMapping(rule, "System", pool, id, description);
108         String oldRuleID = accessStore.getRuleID(id, pool);
109         if (oldRuleID == null || oldRuleID.equals("")) {
110             accessStore.createAccessDefinition(ruleMapping);
111         } else {
112             accessStore.updateAccessDefinition(ruleMapping);
113         }
114     }
115 
116     @Override
117     public void addRule(String permission, Element rule, String description) {
118         addRule(POOL_PRIVILEGE_ID, permission, rule, description);
119     }
120 
121     @Override
122     public void removeRule(String id, String pool) throws MCRException {
123         MCRRuleMapping ruleMapping = accessStore.getAccessDefinition(pool, id);
124         accessStore.deleteAccessDefinition(ruleMapping);
125     }
126 
127     @Override
128     public void removeRule(String permission) throws MCRException {
129         removeRule(POOL_PRIVILEGE_ID, permission);
130     }
131 
132     @Override
133     public void removeAllRules(String id) throws MCRException {
134         for (String pool : accessStore.getPoolsForObject(id)) {
135             removeRule(id, pool);
136         }
137     }
138 
139     @Override
140     public void updateRule(String id, String pool, Element rule, String description) throws MCRException {
141         MCRRuleMapping ruleMapping = getAutoGeneratedRuleMapping(rule, "System", pool, id, description);
142         String oldRuleID = accessStore.getRuleID(id, pool);
143         if (oldRuleID == null || oldRuleID.equals("")) {
144             LOGGER.debug(
145                 "updateRule called for id <{}> and pool <{}>, but no rule is existing, so new rule was created", id,
146                 pool);
147             accessStore.createAccessDefinition(ruleMapping);
148         } else {
149             accessStore.updateAccessDefinition(ruleMapping);
150         }
151     }
152 
153     @Override
154     public void updateRule(String permission, Element rule, String description) throws MCRException {
155         updateRule(POOL_PRIVILEGE_ID, permission, rule, description);
156     }
157 
158     @Override
159     public boolean checkPermission(String id, String permission, MCRUserInformation userInfo) {
160         return checkAccess(id, permission, userInfo, null);
161     }
162 
163     @Override
164     public boolean checkPermission(String permission) {
165         LOGGER.debug("Execute MCRAccessControlSystem checkPermission for permission {}", permission);
166         boolean ret = checkPermission(POOL_PRIVILEGE_ID, permission);
167         LOGGER.debug("Execute MCRAccessControlSystem checkPermission result: {}", String.valueOf(ret));
168         return ret;
169     }
170 
171     @Override
172     @Deprecated
173     public boolean checkPermissionForUser(String permission, String userID) {
174         return checkAccess(POOL_PRIVILEGE_ID, permission, userID, null);
175     }
176 
177     @Override
178     public boolean checkPermissionForUser(String permission, MCRUserInformation userInfo) {
179         return checkAccess(POOL_PRIVILEGE_ID, permission, userInfo, null);
180     }
181 
182     @Override
183     public boolean checkPermission(Element rule) {
184         MCRSession session = MCRSessionMgr.getCurrentSession();
185         String ruleStr = getNormalizedRuleString(rule);
186         MCRAccessRule accessRule = new MCRAccessRule(null, "System", new Date(), ruleStr, "");
187         try {
188             return accessRule.checkAccess(session.getUserInformation(), new Date(), new MCRIPAddress(
189                 session.getCurrentIP()));
190         } catch (MCRException | UnknownHostException e) {
191             // only return true if access is allowed, we dont know this
192             LOGGER.debug("Error while checking rule.", e);
193             return false;
194         }
195     }
196 
197     @Override
198     public Element getRule(String objID, String permission) {
199         MCRAccessRule accessRule = getAccessRule(objID, permission);
200         MCRRuleParser parser = new MCRRuleParser();
201         Element rule = parser.parse(accessRule.rule).toXML();
202         Element condition = new Element("condition");
203         condition.setAttribute("format", "xml");
204         if (rule != null) {
205             condition.addContent(rule);
206         }
207         return condition;
208     }
209 
210     @Override
211     public Element getRule(String permission) {
212         return getRule(POOL_PRIVILEGE_ID, permission);
213     }
214 
215     @Override
216     public String getRuleDescription(String permission) {
217         return getRuleDescription(POOL_PRIVILEGE_ID, permission);
218     }
219 
220     @Override
221     public String getRuleDescription(String objID, String permission) {
222         MCRAccessRule accessRule = getAccessRule(objID, permission);
223         if (accessRule != null && accessRule.getDescription() != null) {
224             return accessRule.getDescription();
225         }
226         return "";
227     }
228 
229     @Override
230     public Collection<String> getPermissionsForID(String objid) {
231         return accessStore.getPoolsForObject(objid);
232     }
233 
234     @Override
235     public Collection<String> getPermissions() {
236         return accessStore.getPoolsForObject(POOL_PRIVILEGE_ID);
237     }
238 
239     @Override
240     public boolean hasRule(String id, String permission) {
241         return accessStore.existsRule(id, permission);
242     }
243 
244     @Override
245     public boolean hasRule(String id) {
246         return hasRule(id, null);
247     }
248 
249     @Override
250     public Collection<String> getAllControlledIDs() {
251         return accessStore.getDistinctStringIDs();
252     }
253 
254     // not extended methods
255 
256     public boolean isDisabled() {
257         return disabled;
258     }
259 
260     @Override
261     public MCRAccessRule getAccessRule(String objID, String pool) {
262         if (disabled) {
263             return dummyRule;
264         }
265         LOGGER.debug("accessStore.getRuleID()");
266         String ruleID = accessStore.getRuleID(objID, pool);
267         if (ruleID == null) {
268             LOGGER.debug("accessStore.getRuleID() done with null");
269             return null;
270         } else {
271             LOGGER.debug("accessStore.getRuleID() done with {}", ruleID);
272         }
273         return ruleStore.getRule(ruleID);
274     }
275 
276     /**
277      * Validator methods to validate access definition for given object and pool
278      *
279      * @param permission
280      *            poolname as string
281      * @param objID
282      *            MCRObjectID as string
283      * @param userID
284      *            MCRUser
285      * @param ip
286      *            ip-Address
287      * @return true if access is granted according to defined access rules
288      */
289     @Deprecated
290     public boolean checkAccess(String objID, String permission, String userID, MCRIPAddress ip) {
291         Date date = new Date();
292         LOGGER.debug("getAccess()");
293         MCRAccessRule rule = getAccessRule(objID, permission);
294         LOGGER.debug("getAccess() is done");
295         if (rule == null) {
296             return userID.equals(MCRSystemUserInformation.getSuperUserInstance().getUserID());
297         }
298         return rule.checkAccess(userID, date, ip);
299     }
300 
301     /**
302      * Validator methods to validate access definition for given object and pool
303      *
304      * @param permission
305      *            poolname as string
306      * @param objID
307      *            MCRObjectID as string
308      * @param userInfo
309      *            MCRUser
310      * @param ip
311      *            ip-Address
312      * @return true if access is granted according to defined access rules
313      */
314     public boolean checkAccess(String objID, String permission, MCRUserInformation userInfo, MCRIPAddress ip) {
315         Date date = new Date();
316         LOGGER.debug("getAccess()");
317         MCRAccessRule rule = getAccessRule(objID, permission);
318         LOGGER.debug("getAccess() is done");
319         if (rule == null) {
320             return userInfo.getUserID().equals(MCRSystemUserInformation.getSuperUserInstance().getUserID());
321         }
322         return rule.checkAccess(userInfo, date, ip);
323     }
324 
325     /**
326      * method that delivers the next free ruleID for a given Prefix and sets the counter to counter + 1
327      *
328      * @param prefix
329      *            String
330      * @return String
331      */
332     public synchronized String getNextFreeRuleID(String prefix) {
333         int nextFreeID;
334         String sNextFreeID;
335         if (nextFreeRuleID.containsKey(prefix)) {
336             nextFreeID = nextFreeRuleID.get(prefix);
337         } else {
338             nextFreeID = ruleStore.getNextFreeRuleID(prefix);
339         }
340         sNextFreeID = LEXICOGRAPHICAL_PATTERN + nextFreeID;
341         sNextFreeID = sNextFreeID.substring(sNextFreeID.length() - LEXICOGRAPHICAL_PATTERN.length());
342         nextFreeRuleID.put(prefix, nextFreeID + 1);
343         return prefix + sNextFreeID;
344     }
345 
346     /**
347      * delivers the rule as string, after normalizing it via sorting with MCRAccessConditionsComparator
348      *
349      * @param rule
350      *            Jdom-Element
351      * @return String
352      */
353     @Override
354     public String getNormalizedRuleString(Element rule) {
355         if (rule.getChildren() == null || rule.getChildren().size() == 0) {
356             return "false";
357         }
358         Element normalizedRule = normalize(rule.getChildren().get(0));
359         MCRRuleParser parser = new MCRRuleParser();
360         return parser.parse(normalizedRule).toString();
361     }
362 
363     /**
364      * returns a auto-generated MCRRuleMapping, needed to create Access Definitions
365      *
366      * @param rule
367      *            JDOM-Representation of a MCRAccess Rule
368      * @param creator
369      *            String
370      * @param pool
371      *            String
372      * @param id
373      *            String
374      * @return MCRRuleMapping
375      */
376     public MCRRuleMapping getAutoGeneratedRuleMapping(Element rule, String creator, String pool, String id,
377         String description) {
378         String ruleString = getNormalizedRuleString(rule);
379         String ruleID = ruleIDTable.get(ruleString);
380         if (ruleID == null || ruleID.length() == 0) {
381             Collection<String> existingIDs = ruleStore.retrieveRuleIDs(ruleString, description);
382             if (existingIDs != null && existingIDs.size() > 0) {
383                 // rule yet exists
384                 ruleID = existingIDs.iterator().next();
385             } else {
386                 ruleID = getNextFreeRuleID(SYSTEM_RULE_PREFIX);
387                 MCRAccessRule accessRule = new MCRAccessRule(ruleID, creator, new Date(), ruleString, description);
388                 ruleStore.createRule(accessRule);
389             }
390             ruleIDTable.put(ruleString, ruleID);
391         }
392         MCRRuleMapping ruleMapping = new MCRRuleMapping();
393         ruleMapping.setCreator(creator);
394         ruleMapping.setCreationdate(new Date());
395         ruleMapping.setPool(pool);
396         ruleMapping.setRuleId(ruleID);
397         ruleMapping.setObjId(id);
398         return ruleMapping;
399     }
400 
401     /**
402      * method, that normalizes the jdom-representation of a mycore access condition
403      *
404      * @param rule
405      *            condition-JDOM of an access-rule
406      * @return the normalized JDOM-Rule
407      */
408     public Element normalize(Element rule) {
409         Element newRule = new Element(rule.getName());
410         rule.getAttributes()
411             .stream()
412             .map(Attribute::clone)
413             .forEach(newRule::setAttribute);
414         rule.getChildren()
415             .stream()
416             .map(Element::clone)
417             .map(this::normalize)
418             .sorted(MCRAccessControlSystem::compareAccessConditions)
419             .forEachOrdered(newRule::addContent);
420         return newRule;
421     }
422 
423     /**
424      * A Comparator for the Condition Elements for normalizing the access conditions
425      */
426     private static int compareAccessConditions(Element el0, Element el1) {
427         String nameEl0 = el0.getName();
428         String nameEl1 = el1.getName();
429         int nameCompare = nameEl0.compareTo(nameEl1);
430         // order "boolean" before "condition"
431         if (nameCompare != 0) {
432             return nameCompare;
433         }
434         if (nameEl0.equals("boolean")) {
435             String opEl0 = el0.getAttributeValue("operator");
436             String opEl1 = el0.getAttributeValue("operator");
437             return opEl0.compareToIgnoreCase(opEl1);
438         } else if (nameEl0.equals("condition")) {
439             String fieldEl0 = el0.getAttributeValue("field");
440             String fieldEl1 = el1.getAttributeValue("field");
441             int fieldCompare = fieldEl0.compareToIgnoreCase(fieldEl1);
442             if (fieldCompare != 0) {
443                 return fieldCompare;
444             }
445             String valueEl0 = el0.getAttributeValue("value");
446             String valueEl1 = el1.getAttributeValue("value");
447             return valueEl0.compareTo(valueEl1);
448         }
449         return 0;
450     }
451 
452 }