001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.auth.xacml; 019 020import java.io.File; 021import java.io.FileInputStream; 022import java.io.IOException; 023import java.net.URL; 024 025import javax.inject.Inject; 026import javax.jcr.Node; 027import javax.jcr.RepositoryException; 028import javax.jcr.Session; 029import javax.jcr.nodetype.NodeType; 030import javax.jcr.nodetype.NodeTypeIterator; 031import javax.jcr.nodetype.NodeTypeTemplate; 032 033import org.fcrepo.http.commons.session.SessionFactory; 034import org.fcrepo.kernel.api.FedoraSession; 035import org.fcrepo.kernel.api.exception.InvalidChecksumException; 036import org.fcrepo.kernel.api.models.FedoraBinary; 037import org.fcrepo.kernel.api.services.BinaryService; 038 039import org.apache.commons.io.FileUtils; 040import org.modeshape.jcr.api.nodetype.NodeTypeManager; 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044import com.google.common.collect.ImmutableList; 045 046import static org.fcrepo.kernel.modeshape.FedoraSessionImpl.getJcrSession; 047 048/** 049 * Sets up node types and default policies for the XACML Authorization Delegate. 050 * 051 * @author Gregory Jansen 052 */ 053public class XACMLWorkspaceInitializer { 054 055 /** 056 * Class-level logger. 057 */ 058 private static final Logger LOGGER = LoggerFactory.getLogger(XACMLWorkspaceInitializer.class); 059 060 /** 061 * Fedora's ModeShape session factory. 062 */ 063 @Inject 064 private SessionFactory sessionFactory; 065 066 @Inject 067 private BinaryService binaryService; 068 069 private final File initialPoliciesDirectory; 070 071 private final File initialRootPolicyFile; 072 073 /** 074 * Constructor 075 * 076 * @param initialPoliciesDirectory of default policies 077 * @param initialRootPolicyFile defining root policy 078 */ 079 public XACMLWorkspaceInitializer(final File initialPoliciesDirectory, final File initialRootPolicyFile) { 080 if (null == initialPoliciesDirectory) { 081 throw new IllegalArgumentException("InitialPolicyDirectory is null!"); 082 } 083 if (null == initialPoliciesDirectory.list() || 0 == initialPoliciesDirectory.list().length) { 084 throw new IllegalArgumentException("InitialPolicyDirectory does not exist or is empty! " + 085 initialPoliciesDirectory.getAbsolutePath()); 086 } 087 if (null == initialRootPolicyFile || !initialRootPolicyFile.exists()) { 088 throw new IllegalArgumentException("InitialRootPolicyFile is null or does not exist!"); 089 } 090 091 this.initialPoliciesDirectory = initialPoliciesDirectory; 092 this.initialRootPolicyFile = initialRootPolicyFile; 093 } 094 095 /** 096 * Initializes default policies. 097 */ 098 public void init() { 099 doInit(false); 100 } 101 102 /** 103 * Initializes node types and default policies - for Integration Tests! 104 */ 105 public void initTest() { 106 doInit(true); 107 } 108 109 private void doInit(final boolean test) { 110 // Do not "registerNodeTypes" because the xacml-policy.cnd introduces a cyclical dependency with the main 111 // fedora-node-types.cnd that causes an exception on repository restart. 112 if (test) { 113 registerNodeTypes(); 114 } 115 loadInitialPolicies(); 116 linkRootToPolicy(); 117 } 118 119 private void registerNodeTypes() { 120 Session session = null; 121 try { 122 session = getJcrSession(sessionFactory.getInternalSession()); 123 final NodeTypeManager mgr = (NodeTypeManager) session.getWorkspace().getNodeTypeManager(); 124 final URL cnd = XACMLWorkspaceInitializer.class.getResource("/cnd/xacml-policy.cnd"); 125 final NodeTypeIterator nti = mgr.registerNodeTypes(cnd, true); 126 while (nti.hasNext()) { 127 final NodeType nt = nti.nextNodeType(); 128 LOGGER.debug("registered node type: {}", nt.getName()); 129 } 130 131 // Add "authz:xacmlAssignable" mixin to "fedora:Resource" type 132 final NodeType nodeType = mgr.getNodeType("fedora:Resource"); 133 final NodeTypeTemplate nodeTypeTemplate = mgr.createNodeTypeTemplate(nodeType); 134 final String[] superTypes = nodeType.getDeclaredSupertypeNames(); 135 final ImmutableList.Builder<String> listBuilder = ImmutableList.builder(); 136 listBuilder.add(superTypes); 137 listBuilder.add("authz:xacmlAssignable"); 138 final ImmutableList<String> newSuperTypes = listBuilder.build(); 139 nodeTypeTemplate.setDeclaredSuperTypeNames(newSuperTypes.toArray(new String[newSuperTypes.size()])); 140 141 mgr.registerNodeType(nodeTypeTemplate, true); 142 143 session.save(); 144 LOGGER.debug("Registered XACML policy node types"); 145 } catch (final RepositoryException | IOException e) { 146 throw new Error("Cannot register XACML policy node types", e); 147 } finally { 148 if (session != null) { 149 session.logout(); 150 } 151 } 152 } 153 154 /** 155 * Create nodes for the default XACML policy set. Policies are created at paths according to their IDs. 156 */ 157 private void loadInitialPolicies() { 158 FedoraSession session = null; 159 try { 160 session = sessionFactory.getInternalSession(); 161 for (final File p : initialPoliciesDirectory.listFiles()) { 162 final String id = PolicyUtil.getID(FileUtils.openInputStream(p)); 163 final String repoPath = PolicyUtil.getPathForId(id); 164 final FedoraBinary binary = binaryService.findOrCreate(session, repoPath); 165 try (FileInputStream stream = new FileInputStream(p)) { 166 binary.setContent(stream, "application/xml", null, p.getName(), null); 167 } 168 LOGGER.info("Add initial policy {} at {}", p.getAbsolutePath(), binary.getPath()); 169 } 170 session.commit(); 171 } catch (final InvalidChecksumException | IOException e) { 172 throw new Error("Cannot create default root policies", e); 173 } finally { 174 if (session != null) { 175 session.expire(); 176 } 177 } 178 } 179 180 /** 181 * Set the policy that is effective at the root node. 182 */ 183 private void linkRootToPolicy() { 184 Session session = null; 185 try { 186 session = getJcrSession(sessionFactory.getInternalSession()); 187 session.getRootNode().addMixin("authz:xacmlAssignable"); 188 final String id = PolicyUtil.getID(FileUtils.openInputStream(initialRootPolicyFile)); 189 final String repoPath = PolicyUtil.getPathForId(id); 190 final Node globalPolicy = session.getNode(repoPath); 191 session.getRootNode().setProperty("authz:policy", globalPolicy); 192 session.save(); 193 } catch (final RepositoryException | IOException e) { 194 throw new Error("Cannot configure root mix-in or policy", e); 195 } finally { 196 if (session != null) { 197 session.logout(); 198 } 199 } 200 } 201}