001    /*
002     * 
003     * $Revision$ $Date$
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, in a file called gpl.txt or license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     */
023    
024    package org.mycore.access.mcrimpl;
025    
026    import java.net.UnknownHostException;
027    import java.util.ArrayList;
028    import java.util.Collection;
029    import java.util.Collections;
030    import java.util.Comparator;
031    import java.util.Date;
032    import java.util.HashMap;
033    import java.util.Hashtable;
034    import java.util.Iterator;
035    import java.util.List;
036    
037    import org.jdom.Attribute;
038    import org.jdom.Element;
039    
040    import org.mycore.access.MCRAccessInterface;
041    import org.mycore.access.MCRAccessBaseImpl;
042    import org.mycore.common.MCRCache;
043    import org.mycore.common.MCRConfiguration;
044    import org.mycore.common.MCRException;
045    import org.mycore.common.MCRSession;
046    import org.mycore.common.MCRSessionMgr;
047    import org.mycore.user.MCRUser;
048    import org.mycore.user.MCRUserMgr;
049    
050    /**
051     * MyCoRe-Standard Implementation of the MCRAccessInterface
052     * 
053     * Maps object ids to rules
054     * 
055     * @author Matthias Kramm
056     * @author Heiko Helmbrecht
057     */
058    public class MCRAccessControlSystem extends MCRAccessBaseImpl {
059    
060        public static final String systemRulePrefix = "SYSTEMRULE";
061    
062        public static final String poolPrivilegeID = "POOLPRIVILEGE";
063    
064        public static final String lexicographicalPattern = "0000000000";
065    
066        static String superuserID = MCRConfiguration.instance().getString("MCR.Users.Superuser.UserName", "mcradmin");
067    
068        static MCRCache cache;
069    
070        MCRAccessStore accessStore;
071    
072        MCRRuleStore ruleStore;
073    
074        MCRAccessRule dummyRule;
075    
076        boolean disabled = false;
077    
078        static Hashtable<String, String> ruleIDTable = new Hashtable<String, String>();
079    
080        private MCRAccessControlSystem() {
081            MCRConfiguration config = MCRConfiguration.instance();
082            int size = config.getInt("MCR.AccessPool.CacheSize", 2048);
083            String pools = config.getString("MCR.Access.AccessPermissions", "read,write,delete");
084    
085            if (pools.trim().length() == 0) {
086                disabled = true;
087            }
088    
089            cache = new MCRCache(size, "Access Rules");
090            accessStore = MCRAccessStore.getInstance();
091            ruleStore = MCRRuleStore.getInstance();
092            accessComp = new MCRAccessConditionsComparator();
093    
094            nextFreeRuleID = new HashMap<String, Integer>();
095    
096            dummyRule = new MCRAccessRule(null, null, null, null, "dummy rule, always true");
097        }
098    
099        private static MCRAccessControlSystem singleton;
100    
101        private static Comparator<Element> accessComp;
102    
103        private static HashMap<String, Integer> nextFreeRuleID;
104    
105        // extended methods
106        public static synchronized MCRAccessInterface instance() {
107            if (singleton == null) {
108                singleton = new MCRAccessControlSystem();
109            }
110            return singleton;
111        }
112    
113        public void createRule(String ruleString, String creator, String description){
114            String ruleID = getNextFreeRuleID(systemRulePrefix);
115            MCRAccessRule accessRule = new MCRAccessRule(ruleID, creator, new Date(), ruleString, description);
116            ruleStore.createRule(accessRule);
117        }
118    
119        public void createRule(Element rule, String creator, String description){
120            createRule(getNormalizedRuleString(rule), creator, description);
121        }
122        
123        public void addRule(String id, String pool, Element rule, String description) throws MCRException {
124            MCRRuleMapping ruleMapping = getAutoGeneratedRuleMapping(rule, "System", pool, id, description);
125            String oldRuleID = accessStore.getRuleID(id, pool);
126            if (oldRuleID == null || oldRuleID.equals("")) {
127                accessStore.createAccessDefinition(ruleMapping);
128            } else {
129                accessStore.updateAccessDefinition(ruleMapping);
130            }
131            return;
132        }
133    
134        public void addRule(String permission, org.jdom.Element rule, String description) {
135            addRule(poolPrivilegeID, permission, rule, description);
136        }
137    
138        public void removeRule(String id, String pool) throws MCRException {
139            MCRRuleMapping ruleMapping = accessStore.getAccessDefinition(pool, id);
140            accessStore.deleteAccessDefinition(ruleMapping);
141        }
142    
143        public void removeRule(String permission) throws MCRException {
144            removeRule(poolPrivilegeID, permission);
145        }
146    
147        public void removeAllRules(String id) throws MCRException {
148            for (String pool: accessStore.getPoolsForObject(id)) {
149                removeRule(id, pool);
150            }
151        }
152    
153        public void updateRule(String id, String pool, org.jdom.Element rule, String description) throws MCRException {
154            MCRRuleMapping ruleMapping = getAutoGeneratedRuleMapping(rule, "System", pool, id, description);
155            String oldRuleID = accessStore.getRuleID(id, pool);
156            if (oldRuleID == null || oldRuleID.equals("")) {
157                LOGGER.debug("updateRule called for id <" + id + "> and pool <" + pool + ">, but no rule is existing, so new rule was created");
158                accessStore.createAccessDefinition(ruleMapping);
159            } else {
160                accessStore.updateAccessDefinition(ruleMapping);
161            }
162            return;
163        }
164    
165        public void updateRule(String permission, Element rule, String description) throws MCRException {
166            updateRule(poolPrivilegeID, permission, rule, description);
167        }
168    
169        public boolean checkPermission(String id, String permission) {
170            long start = System.currentTimeMillis();
171            MCRSession session = MCRSessionMgr.getCurrentSession();
172            MCRUser user = MCRUserMgr.instance().retrieveUser(session.getCurrentUserID());
173            LOGGER.debug("Get current User took: " + (System.currentTimeMillis() - start));
174            try {
175                boolean returns = checkAccess(id, permission, user, new MCRIPAddress(session.getCurrentIP()));
176                LOGGER.debug("Check " + permission + " on " + id + " took:" + (System.currentTimeMillis() - start));
177                return returns;
178            } catch (MCRException e) {
179                // only return true if access is allowed, we dont know this
180                LOGGER.debug("Error while checking rule.", e);
181                return false;
182            } catch (UnknownHostException e) {
183                // only return true if access is allowed, we dont know this
184                LOGGER.debug("Error while checking rule.", e);
185                return false;
186            }
187        }
188    
189        public boolean checkPermission(String id, String permission, MCRUser user) {
190            return checkAccess(id, permission, user, null);
191        }
192    
193        public boolean checkPermission(String permission) {
194            LOGGER.debug("Execute MCRAccessControlSystem checkPermission for permission " + permission);
195            boolean ret = checkPermission(poolPrivilegeID, permission);
196            LOGGER.debug("Execute MCRAccessControlSystem checkPermission result: " + (new Boolean(ret)).toString());
197            return ret;
198        }
199    
200        public boolean checkPermission(String permission, MCRUser user) {
201            return checkAccess(poolPrivilegeID, permission, user, null);
202        }
203    
204        public boolean checkPermission(Element rule) {
205            MCRSession session = MCRSessionMgr.getCurrentSession();
206            String ruleStr = getNormalizedRuleString(rule);
207            MCRAccessRule accessRule = new MCRAccessRule(null, "System", new Date(), ruleStr, "");
208            try {
209                return accessRule.checkAccess(MCRUserMgr.instance().retrieveUser(session.getCurrentUserID()), new Date(), new MCRIPAddress(session.getCurrentIP()));
210            } catch (MCRException e) {
211                // only return true if access is allowed, we dont know this
212                LOGGER.debug("Error while checking rule.", e);
213                return false;
214            } catch (UnknownHostException e) {
215                // only return true if access is allowed, we dont know this
216                LOGGER.debug("Error while checking rule.", e);
217                return false;
218            }
219        }
220    
221        public Element getRule(String objID, String permission) {
222            MCRAccessRule accessRule = getAccess(objID, permission);
223            MCRRuleParser parser = new MCRRuleParser();
224            Element rule = parser.parse(accessRule.rule).toXML();
225            Element condition = new Element("condition");
226            condition.setAttribute("format", "xml");
227            if (rule != null) {
228                condition.addContent(rule);
229            }
230            return condition;
231        }
232    
233        public Element getRule(String permission) {
234            return getRule(poolPrivilegeID, permission);
235        }
236    
237        public String getRuleDescription(String permission) {
238            return getRuleDescription(poolPrivilegeID, permission);
239        }
240    
241        public String getRuleDescription(String objID, String permission) {
242            MCRAccessRule accessRule = getAccess(objID, permission);
243            if (accessRule != null && accessRule.getDescription() != null)
244                return accessRule.getDescription();
245            return "";
246        }
247    
248        public Collection<String> getPermissionsForID(String objid) {
249            Collection<String> ret = accessStore.getPoolsForObject(objid);
250            return ret;
251        }
252    
253        public Collection<String> getPermissions() {
254            return accessStore.getPoolsForObject(poolPrivilegeID);
255        }
256    
257        public boolean hasRule(String id, String permission) {
258            return accessStore.existsRule(id, permission);
259        }
260    
261        public boolean hasRule(String id) {
262            return hasRule(id, null);
263        }
264    
265        public Collection<String> getAllControlledIDs() {
266            return accessStore.getDistinctStringIDs();
267        }
268    
269        // not extended methods
270    
271        public boolean isDisabled() {
272            return disabled;
273        }
274    
275        public MCRAccessRule getAccess(String objID, String pool) {
276            if (disabled) {
277                return dummyRule;
278            }
279            LOGGER.debug("accessStore.getRuleID()");
280            String ruleID = accessStore.getRuleID(objID, pool);
281            LOGGER.debug("accessStore.getRuleID() done.");
282            if (ruleID == null) {
283                return null;
284            }
285            MCRAccessRule a = (MCRAccessRule) cache.get(ruleID);
286            if (a == null) {
287                LOGGER.debug("ruleStore.getRule()");
288                a = ruleStore.getRule(ruleID);
289                LOGGER.debug("ruleStore.getRule() done");
290                if (a != null) {
291                    cache.put(ruleID, a);
292                }
293            }
294            return a;
295        }
296    
297        /**
298         * Validator methods to validate access definition for given object and pool
299         * 
300         * @param permission
301         *            poolname as string
302         * @param objID
303         *            MCRObjectID as string
304         * @param user
305         *            MCRUser
306         * @param ip
307         *            ip-Address
308         * @return true if access is granted according to defined access rules
309         */
310        public boolean checkAccess(String objID, String permission, MCRUser user, MCRIPAddress ip) {
311            Date date = new Date();
312            LOGGER.debug("getAccess()");
313            MCRAccessRule rule = getAccess(objID, permission);
314            LOGGER.debug("getAccess() is done");
315            if (rule == null) {
316                if (user.getID().equals(superuserID)) {
317                    return true;
318                }
319                return false;
320            }
321            return rule.checkAccess(user, date, ip);
322        }
323    
324        /**
325         * method that delivers the next free ruleID for a given Prefix and sets the
326         * 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).intValue();
337            } else {
338                nextFreeID = ruleStore.getNextFreeRuleID(prefix);
339            }
340            sNextFreeID = lexicographicalPattern + String.valueOf(nextFreeID);
341            sNextFreeID = sNextFreeID.substring(sNextFreeID.length() - lexicographicalPattern.length());
342            nextFreeRuleID.put(prefix, new Integer(nextFreeID + 1));
343            return prefix + sNextFreeID;
344        }
345    
346        /**
347         * delivers the rule as string, after normalizing it via sorting with
348         * MCRAccessConditionsComparator
349         * 
350         * @param rule
351         *            Jdom-Element
352         * @return String
353         */
354        public String getNormalizedRuleString(Element rule) {
355            if (rule.getChildren() == null || rule.getChildren().size() == 0) {
356                return "false";
357            }
358            Element normalizedRule = normalize((Element) 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
365         * Definitions
366         * 
367         * @param rule
368         *            JDOM-Representation of a MCRAccess Rule
369         * @param creator
370         *            String
371         * @param pool
372         *            String
373         * @param id
374         *            String
375         * @return MCRRuleMapping
376         */
377        public MCRRuleMapping getAutoGeneratedRuleMapping(Element rule, String creator, String pool, String id, 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(systemRulePrefix);
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
403         * condition
404         * 
405         * @param rule
406         *            condition-JDOM of an access-rule
407         * @return the normalized JDOM-Rule
408         */
409        @SuppressWarnings("unchecked")
410        public Element normalize(Element rule) {
411            Element newRule = new Element(rule.getName());
412            for (Iterator it = rule.getAttributes().iterator(); it.hasNext();) {
413                Attribute att = (Attribute) it.next();
414                newRule.setAttribute((Attribute) att.clone());
415            }
416            List children = rule.getChildren();
417            if (children == null || children.size() == 0)
418                return newRule;
419            List<Element> newList = new ArrayList<Element>();
420            for (Iterator it = children.iterator(); it.hasNext();) {
421                Element el = (Element) it.next();
422                newList.add((Element) el.clone());
423            }
424            Collections.sort(newList, accessComp);
425            for (Iterator<Element> it = newList.iterator(); it.hasNext();) {
426                Element el = it.next();
427                newRule.addContent(normalize(el));
428            }
429            return newRule;
430        }
431    
432        /**
433         * A Comparator for the Condition Elements for normalizing the access
434         * conditions
435         * 
436         */
437        private class MCRAccessConditionsComparator implements Comparator<Element> {
438    
439            public int compare(Element el0, Element el1) {
440                String nameEl0 = el0.getName().toLowerCase();
441                String nameEl1 = el1.getName().toLowerCase();
442                int nameCompare = nameEl0.compareTo(nameEl1);
443                // order "boolean" before "condition"
444                if (nameCompare != 0)
445                    return nameCompare;
446                if (nameEl0.equals("boolean")) {
447                    String opEl0 = el0.getAttributeValue("operator").toLowerCase();
448                    String opEl1 = el0.getAttributeValue("operator").toLowerCase();
449                    return opEl0.compareTo(opEl1);
450                } else if (nameEl0.equals("condition")) {
451                    String fieldEl0 = el0.getAttributeValue("field").toLowerCase();
452                    String fieldEl1 = el1.getAttributeValue("field").toLowerCase();
453                    int fieldCompare = fieldEl0.compareTo(fieldEl1);
454                    if (fieldCompare != 0)
455                        return fieldCompare;
456                    String valueEl0 = el0.getAttributeValue("value");
457                    String valueEl1 = el1.getAttributeValue("value");
458                    return valueEl0.compareTo(valueEl1);
459                }
460                return 0;
461            }
462        }
463    
464            public static MCRCache getCache() {
465                    return cache;
466            };
467    
468    };