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  package org.mycore.access.facts;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Objects;
28  import java.util.concurrent.TimeUnit;
29  
30  import org.apache.logging.log4j.LogManager;
31  import org.apache.logging.log4j.Logger;
32  import org.jdom2.Element;
33  import org.jdom2.output.Format;
34  import org.jdom2.output.XMLOutputter;
35  import org.mycore.access.MCRAccessInterface;
36  import org.mycore.access.facts.fact.MCRObjectIDFact;
37  import org.mycore.access.facts.fact.MCRStringFact;
38  import org.mycore.access.facts.model.MCRCombinedCondition;
39  import org.mycore.access.facts.model.MCRCondition;
40  import org.mycore.access.facts.model.MCRFact;
41  import org.mycore.access.facts.model.MCRFactComputable;
42  import org.mycore.access.strategies.MCRAccessCheckStrategy;
43  import org.mycore.common.MCRSessionMgr;
44  import org.mycore.common.MCRUserInformation;
45  import org.mycore.common.config.MCRConfiguration2;
46  import org.mycore.common.config.MCRConfigurationDir;
47  import org.mycore.common.config.annotation.MCRPostConstruction;
48  import org.mycore.common.config.annotation.MCRProperty;
49  import org.mycore.common.content.MCRJDOMContent;
50  import org.mycore.common.xml.MCRURIResolver;
51  import org.mycore.datamodel.classifications2.MCRCategoryDAOFactory;
52  import org.mycore.datamodel.classifications2.MCRCategoryID;
53  import org.mycore.datamodel.metadata.MCRMetadataManager;
54  import org.mycore.datamodel.metadata.MCRObjectID;
55  
56  import jakarta.inject.Singleton;
57  
58  /**
59   * base class for XML fact based access system
60   * 
61   * enabled it with the 2 properties:
62   * MCR.Access.Class=org.mycore.access.facts.MCRFactsAccessSystem
63   * MCR.Access.Strategy.Class=org.mycore.access.facts.MCRFactsAccessSystem
64   * 
65   */
66  @Singleton
67  public class MCRFactsAccessSystem implements MCRAccessInterface, MCRAccessCheckStrategy {
68  
69      private static String RESOLVED_RULES_FILE_NAME = "rules.resolved.xml";
70  
71      protected static final Logger LOGGER = LogManager.getLogger();
72  
73      private MCRCondition rules;
74  
75      private Collection<MCRFactComputable<MCRFact<?>>> computers;
76  
77      //RS: when introducing this feature in 2021.06.LTS it needed to be configured twice
78      //(as access system and as strategy). To simplify things during the transition period 
79      //we are going to use the base property to initialize the rulesURI for both cases
80      //By using the property MCR.Access.Strategy.RulesURI it could be overwritten if used for strategy.
81      private String rulesURI = MCRConfiguration2.getString("MCR.Access.RulesURI").orElse("resource:rules.xml");
82  
83      private Map<String, String> properties;
84  
85      @MCRPostConstruction
86      public void init(String property) {
87          rules = buildRulesFromXML();
88          computers = buildComputersFromRules();
89      }
90  
91      private Collection<MCRFactComputable<MCRFact<?>>> buildComputersFromRules() {
92          Map<String, MCRFactComputable<MCRFact<?>>> collectedComputers = new HashMap<>();
93          collectComputers(rules, collectedComputers);
94          return collectedComputers.values();
95      }
96  
97      @SuppressWarnings("unchecked")
98      private void collectComputers(MCRCondition coll, Map<String, MCRFactComputable<MCRFact<?>>> computers) {
99          if (coll instanceof MCRFactComputable<?>
100             && !computers.containsKey(((MCRFactComputable<?>) coll).getFactName())) {
101             computers.put(((MCRFactComputable<?>) coll).getFactName(), (MCRFactComputable<MCRFact<?>>) coll);
102         }
103         if (coll instanceof MCRCombinedCondition) {
104             ((MCRCombinedCondition) coll).getChildConditions().forEach(c -> collectComputers(c, computers));
105         }
106     }
107 
108     @MCRProperty(name = "RulesURI", required = false)
109     public void setRulesURI(String uri) {
110         rulesURI = uri;
111     }
112 
113     public Map<String, String> getProperties() {
114         return properties;
115     }
116 
117     @MCRProperty(name = "*")
118     public void setProperties(Map<String, String> properties) {
119         this.properties = properties;
120     }
121 
122     private MCRCondition buildRulesFromXML() {
123         Element eRules = MCRURIResolver.instance().resolve(rulesURI);
124         Objects.requireNonNull(eRules, "The rulesURI " + rulesURI + " resolved to null!");
125         MCRJDOMContent content = new MCRJDOMContent(eRules);
126         try {
127             File configFile = MCRConfigurationDir.getConfigFile(RESOLVED_RULES_FILE_NAME);
128             if (configFile != null) {
129                 content.sendTo(configFile);
130             } else {
131                 throw new IOException("MCRConfigurationDir is not available!");
132             }
133         } catch (IOException e) {
134             LOGGER.error("Could not write file '" + RESOLVED_RULES_FILE_NAME + "' to config directory", e);
135             LOGGER.info("Rules file is: \n" + new XMLOutputter(Format.getPrettyFormat()).outputString(eRules));
136         }
137         return MCRFactsAccessSystemHelper.parse(eRules);
138     }
139 
140     @Override
141     public boolean checkPermission(String id, String permission) {
142         return this.checkPermission(id, permission, MCRSessionMgr.getCurrentSession().getUserInformation());
143     }
144 
145     @Override
146     public boolean checkPermissionForUser(String permission, MCRUserInformation userInfo) {
147         return false;
148     }
149 
150     public boolean checkPermission(String checkID, String permission, List<MCRFact> baseFacts) {
151         String action = permission.replaceAll("db$", ""); // writedb -> write
152 
153         String target; // metadata|files|webpage
154         String cacheKey;
155 
156         MCRFactsHolder facts = new MCRFactsHolder(computers);
157 
158         baseFacts.forEach(facts::add);
159 
160         if (checkID == null) {
161             cacheKey = action;
162         } else {
163             if (MCRObjectID.isValid(checkID)) {
164                 MCRObjectID mcrId = MCRObjectID.getInstance(checkID);
165                 target = "derivate".equals(mcrId.getTypeId()) ? "files" : "metadata";
166 
167                 if (MCRMetadataManager.exists(mcrId)) {
168                     if ("derivate".equals(mcrId.getTypeId())) {
169                         facts.add(new MCRObjectIDFact("derid", checkID, mcrId));
170                         MCRObjectID mcrobjID = MCRMetadataManager.getObjectId(mcrId, 10, TimeUnit.MINUTES);
171                         if (mcrobjID != null) {
172                             facts.add(new MCRObjectIDFact("objid", checkID, mcrobjID));
173                         }
174                     } else {
175                         facts.add(new MCRObjectIDFact("objid", checkID, mcrId));
176                     }
177                 } else {
178                     LOGGER.debug("There is no object or derivate with id " + mcrId.toString() + " in metadata store");
179                 }
180             } else if (checkID.startsWith("webpage")) {
181                 target = "webpage";
182             } else if (checkID.startsWith("solr")) {
183                 target = "solr";
184             } else if (isCategory(checkID)) {
185                 target = "category";
186             } else {
187                 target = "unknown";
188             }
189             cacheKey = action + " " + checkID + " " + target;
190             facts.add(new MCRStringFact("id", checkID));
191             facts.add(new MCRStringFact("target", target));
192         }
193         facts.add(new MCRStringFact("action", action));
194 
195         LOGGER.debug("Testing {} ", cacheKey);
196 
197         boolean result;
198         if (LOGGER.isDebugEnabled()) {
199             MCRCondition rules = buildRulesFromXML();
200             result = rules.matches(facts);
201             LOGGER.debug("Facts are: {}", facts);
202 
203             Element xmlTree = rules.getBoundElement();
204             String xmlString = new XMLOutputter(Format.getPrettyFormat()).outputString(xmlTree);
205             LOGGER.debug(xmlString);
206         } else {
207             result = rules.matches(facts);
208         }
209         LOGGER.info("Checked permission to {} := {}", cacheKey, result);
210 
211         return result;
212     }
213 
214     private boolean isCategory(String checkID) {
215         if (!MCRCategoryID.isValid(checkID)) {
216             return false;
217         }
218         try {
219             return MCRCategoryDAOFactory.getInstance().exist(MCRCategoryID.fromString(checkID));
220         } catch (IllegalArgumentException e) {
221             return false;
222         }
223     }
224 
225     @Override
226     public boolean checkPermission(final String checkID, String permission, MCRUserInformation userInfo) {
227         return checkPermission(checkID, permission, Collections.emptyList());
228     }
229 
230     @Override
231     public boolean checkPermission(String permission) {
232         return checkPermission(null, permission);
233     }
234 
235 }