Java tutorial
/* * 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. */ package org.apache.nifi.registry.security.authorization.file; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.registry.properties.NiFiRegistryProperties; import org.apache.nifi.registry.properties.util.IdentityMapping; import org.apache.nifi.registry.properties.util.IdentityMappingUtil; import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext; import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider; import org.apache.nifi.registry.security.authorization.Group; import org.apache.nifi.registry.security.authorization.User; import org.apache.nifi.registry.security.authorization.UserAndGroups; import org.apache.nifi.registry.security.authorization.UserGroupProviderInitializationContext; import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext; import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException; import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException; import org.apache.nifi.registry.security.authorization.file.tenants.generated.Groups; import org.apache.nifi.registry.security.authorization.file.tenants.generated.Tenants; import org.apache.nifi.registry.security.authorization.file.tenants.generated.Users; import org.apache.nifi.registry.security.exception.SecurityProviderCreationException; import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException; import org.apache.nifi.registry.util.FileUtils; import org.apache.nifi.registry.util.PropertyValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; public class FileUserGroupProvider implements ConfigurableUserGroupProvider { private static final Logger logger = LoggerFactory.getLogger(FileUserGroupProvider.class); private static final String TENANTS_XSD = "/tenants.xsd"; private static final String JAXB_TENANTS_PATH = "org.apache.nifi.registry.security.authorization.file.tenants.generated"; private static final JAXBContext JAXB_TENANTS_CONTEXT = initializeJaxbContext(JAXB_TENANTS_PATH); /** * Load the JAXBContext. */ private static JAXBContext initializeJaxbContext(final String contextPath) { try { return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader()); //return JAXBContext.newInstance(contextPath); } catch (JAXBException e) { throw new RuntimeException("Unable to create JAXBContext: " + e); } } private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance(); private static final String USER_ELEMENT = "user"; private static final String GROUP_USER_ELEMENT = "groupUser"; private static final String GROUP_ELEMENT = "group"; private static final String IDENTIFIER_ATTR = "identifier"; private static final String IDENTITY_ATTR = "identity"; private static final String NAME_ATTR = "name"; static final String PROP_INITIAL_USER_IDENTITY_PREFIX = "Initial User Identity "; static final String PROP_TENANTS_FILE = "Users File"; static final Pattern INITIAL_USER_IDENTITY_PATTERN = Pattern .compile(PROP_INITIAL_USER_IDENTITY_PREFIX + "\\S+"); private Schema usersSchema; private Schema tenantsSchema; private NiFiRegistryProperties properties; private File tenantsFile; private File restoreTenantsFile; private Set<String> initialUserIdentities; private List<IdentityMapping> identityMappings; private final AtomicReference<UserGroupHolder> userGroupHolder = new AtomicReference<>(); @Override public void initialize(UserGroupProviderInitializationContext initializationContext) throws SecurityProviderCreationException { try { final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); tenantsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(TENANTS_XSD)); //usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD)); } catch (Exception e) { throw new SecurityProviderCreationException(e); } } @Override public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException { try { final PropertyValue tenantsPath = configurationContext.getProperty(PROP_TENANTS_FILE); if (StringUtils.isBlank(tenantsPath.getValue())) { throw new SecurityProviderCreationException("The users file must be specified."); } // get the tenants file and ensure it exists tenantsFile = new File(tenantsPath.getValue()); if (!tenantsFile.exists()) { logger.info("Creating new users file at {}", new Object[] { tenantsFile.getAbsolutePath() }); saveTenants(new Tenants()); } final File tenantsFileDirectory = tenantsFile.getAbsoluteFile().getParentFile(); // extract the identity mappings from nifi-registry.properties if any are provided identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties)); // extract any nifi identities initialUserIdentities = new HashSet<>(); for (Map.Entry<String, String> entry : configurationContext.getProperties().entrySet()) { Matcher matcher = INITIAL_USER_IDENTITY_PATTERN.matcher(entry.getKey()); if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) { initialUserIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings)); } } load(); // if we've copied the authorizations file to a restore directory synchronize it if (restoreTenantsFile != null) { FileUtils.copyFile(tenantsFile, restoreTenantsFile, false, false, logger); } logger.info(String.format("Users/Groups file loaded at %s", new Date().toString())); } catch (IOException | SecurityProviderCreationException | JAXBException | IllegalStateException | SAXException e) { throw new SecurityProviderCreationException(e); } } @Override public Set<User> getUsers() throws AuthorizationAccessException { return userGroupHolder.get().getAllUsers(); } @Override public synchronized User addUser(User user) throws AuthorizationAccessException { if (user == null) { throw new IllegalArgumentException("User cannot be null"); } final org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser = createJAXBUser( user); final UserGroupHolder holder = userGroupHolder.get(); final Tenants tenants = holder.getTenants(); tenants.getUsers().getUser().add(jaxbUser); saveAndRefreshHolder(tenants); return userGroupHolder.get().getUsersById().get(user.getIdentifier()); } @Override public User getUser(String identifier) throws AuthorizationAccessException { if (identifier == null) { return null; } final UserGroupHolder holder = userGroupHolder.get(); return holder.getUsersById().get(identifier); } @Override public synchronized User updateUser(User user) throws AuthorizationAccessException { if (user == null) { throw new IllegalArgumentException("User cannot be null"); } final UserGroupHolder holder = userGroupHolder.get(); final Tenants tenants = holder.getTenants(); final List<org.apache.nifi.registry.security.authorization.file.tenants.generated.User> users = tenants .getUsers().getUser(); // fine the User that needs to be updated org.apache.nifi.registry.security.authorization.file.tenants.generated.User updateUser = null; for (org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser : users) { if (user.getIdentifier().equals(jaxbUser.getIdentifier())) { updateUser = jaxbUser; break; } } // if user wasn't found return null, otherwise update the user and save changes if (updateUser == null) { return null; } else { updateUser.setIdentity(user.getIdentity()); saveAndRefreshHolder(tenants); return userGroupHolder.get().getUsersById().get(user.getIdentifier()); } } @Override public User getUserByIdentity(String identity) throws AuthorizationAccessException { if (identity == null) { return null; } final UserGroupHolder holder = userGroupHolder.get(); return holder.getUsersByIdentity().get(identity); } @Override public synchronized User deleteUser(User user) throws AuthorizationAccessException { if (user == null) { throw new IllegalArgumentException("User cannot be null"); } return deleteUser(user.getIdentifier()); } @Override public synchronized User deleteUser(String userIdentifier) throws AuthorizationAccessException { if (userIdentifier == null) { throw new IllegalArgumentException("User identifier cannot be null"); } final UserGroupHolder holder = userGroupHolder.get(); final User deletedUser = holder.getUsersById().get(userIdentifier); if (deletedUser == null) { return null; } // for each group iterate over the user references and remove the user reference if it matches the user being deleted final Tenants tenants = holder.getTenants(); for (org.apache.nifi.registry.security.authorization.file.tenants.generated.Group group : tenants .getGroups().getGroup()) { Iterator<org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User> groupUserIter = group .getUser().iterator(); while (groupUserIter.hasNext()) { org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User groupUser = groupUserIter .next(); if (groupUser.getIdentifier().equals(userIdentifier)) { groupUserIter.remove(); break; } } } // remove the actual user Iterator<org.apache.nifi.registry.security.authorization.file.tenants.generated.User> iter = tenants .getUsers().getUser().iterator(); while (iter.hasNext()) { org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser = iter.next(); if (userIdentifier.equals(jaxbUser.getIdentifier())) { iter.remove(); break; } } saveAndRefreshHolder(tenants); return deletedUser; } @Override public Set<Group> getGroups() throws AuthorizationAccessException { return userGroupHolder.get().getAllGroups(); } @Override public synchronized Group addGroup(Group group) throws AuthorizationAccessException { if (group == null) { throw new IllegalArgumentException("Group cannot be null"); } final UserGroupHolder holder = userGroupHolder.get(); final Tenants tenants = holder.getTenants(); // create a new JAXB Group based on the incoming Group final org.apache.nifi.registry.security.authorization.file.tenants.generated.Group jaxbGroup = new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group(); jaxbGroup.setIdentifier(group.getIdentifier()); jaxbGroup.setName(group.getName()); // add each user to the group for (String groupUser : group.getUsers()) { org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User(); jaxbGroupUser.setIdentifier(groupUser); jaxbGroup.getUser().add(jaxbGroupUser); } tenants.getGroups().getGroup().add(jaxbGroup); saveAndRefreshHolder(tenants); return userGroupHolder.get().getGroupsById().get(group.getIdentifier()); } @Override public Group getGroup(String identifier) throws AuthorizationAccessException { if (identifier == null) { return null; } return userGroupHolder.get().getGroupsById().get(identifier); } @Override public UserAndGroups getUserAndGroups(final String identity) throws AuthorizationAccessException { final UserGroupHolder holder = userGroupHolder.get(); final User user = holder.getUser(identity); final Set<Group> groups = holder.getGroups(identity); return new UserAndGroups() { @Override public User getUser() { return user; } @Override public Set<Group> getGroups() { return groups; } }; } @Override public synchronized Group updateGroup(Group group) throws AuthorizationAccessException { if (group == null) { throw new IllegalArgumentException("Group cannot be null"); } final UserGroupHolder holder = userGroupHolder.get(); final Tenants tenants = holder.getTenants(); // find the group that needs to be update org.apache.nifi.registry.security.authorization.file.tenants.generated.Group updateGroup = null; for (org.apache.nifi.registry.security.authorization.file.tenants.generated.Group jaxbGroup : tenants .getGroups().getGroup()) { if (jaxbGroup.getIdentifier().equals(group.getIdentifier())) { updateGroup = jaxbGroup; break; } } // if the group wasn't found return null, otherwise update the group and save changes if (updateGroup == null) { return null; } // reset the list of users and add each user to the group updateGroup.getUser().clear(); for (String groupUser : group.getUsers()) { org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User(); jaxbGroupUser.setIdentifier(groupUser); updateGroup.getUser().add(jaxbGroupUser); } updateGroup.setName(group.getName()); saveAndRefreshHolder(tenants); return userGroupHolder.get().getGroupsById().get(group.getIdentifier()); } @Override public synchronized Group deleteGroup(Group group) throws AuthorizationAccessException { if (group == null) { throw new IllegalArgumentException("Group cannot be null"); } return deleteGroup(group.getIdentifier()); } @Override public synchronized Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException { if (groupIdentifier == null) { throw new IllegalArgumentException("Group identifier cannot be null"); } final UserGroupHolder holder = userGroupHolder.get(); final Group deletedGroup = holder.getGroupsById().get(groupIdentifier); if (deletedGroup == null) { return null; } // now remove the actual group from the top-level list of groups final Tenants tenants = holder.getTenants(); Iterator<org.apache.nifi.registry.security.authorization.file.tenants.generated.Group> iter = tenants .getGroups().getGroup().iterator(); while (iter.hasNext()) { org.apache.nifi.registry.security.authorization.file.tenants.generated.Group jaxbGroup = iter.next(); if (groupIdentifier.equals(jaxbGroup.getIdentifier())) { iter.remove(); break; } } saveAndRefreshHolder(tenants); return deletedGroup; } UserGroupHolder getUserGroupHolder() { return userGroupHolder.get(); } @AuthorizerContext public void setNiFiProperties(NiFiRegistryProperties properties) { this.properties = properties; } @Override public synchronized void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { final UsersAndGroups usersAndGroups = parseUsersAndGroups(fingerprint); usersAndGroups.getUsers().forEach(user -> addUser(user)); usersAndGroups.getGroups().forEach(group -> addGroup(group)); } @Override public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException { try { // ensure we understand the proposed fingerprint parseUsersAndGroups(proposedFingerprint); } catch (final AuthorizationAccessException e) { throw new UninheritableAuthorizationsException("Unable to parse the proposed fingerprint: " + e); } final UserGroupHolder usersAndGroups = userGroupHolder.get(); // ensure we are in a proper state to inherit the fingerprint if (!usersAndGroups.getAllUsers().isEmpty() || !usersAndGroups.getAllGroups().isEmpty()) { throw new UninheritableAuthorizationsException( "Proposed fingerprint is not inheritable because the current users and groups is not empty."); } } @Override public String getFingerprint() throws AuthorizationAccessException { final UserGroupHolder usersAndGroups = userGroupHolder.get(); final List<User> users = new ArrayList<>(usersAndGroups.getAllUsers()); Collections.sort(users, Comparator.comparing(User::getIdentifier)); final List<Group> groups = new ArrayList<>(usersAndGroups.getAllGroups()); Collections.sort(groups, Comparator.comparing(Group::getIdentifier)); XMLStreamWriter writer = null; final StringWriter out = new StringWriter(); try { writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out); writer.writeStartDocument(); writer.writeStartElement("tenants"); for (User user : users) { writeUser(writer, user); } for (Group group : groups) { writeGroup(writer, group); } writer.writeEndElement(); writer.writeEndDocument(); writer.flush(); } catch (XMLStreamException e) { throw new AuthorizationAccessException("Unable to generate fingerprint", e); } finally { if (writer != null) { try { writer.close(); } catch (XMLStreamException e) { // nothing to do here } } } return out.toString(); } private UsersAndGroups parseUsersAndGroups(final String fingerprint) { final List<User> users = new ArrayList<>(); final List<Group> groups = new ArrayList<>(); final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8); try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) { final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); final Document document = docBuilder.parse(in); final Element rootElement = document.getDocumentElement(); // parse all the users and add them to the current user group provider NodeList userNodes = rootElement.getElementsByTagName(USER_ELEMENT); for (int i = 0; i < userNodes.getLength(); i++) { Node userNode = userNodes.item(i); users.add(parseUser((Element) userNode)); } // parse all the groups and add them to the current user group provider NodeList groupNodes = rootElement.getElementsByTagName(GROUP_ELEMENT); for (int i = 0; i < groupNodes.getLength(); i++) { Node groupNode = groupNodes.item(i); groups.add(parseGroup((Element) groupNode)); } } catch (SAXException | ParserConfigurationException | IOException e) { throw new AuthorizationAccessException("Unable to parse fingerprint", e); } return new UsersAndGroups(users, groups); } private User parseUser(final Element element) { final User.Builder builder = new User.Builder().identifier(element.getAttribute(IDENTIFIER_ATTR)) .identity(element.getAttribute(IDENTITY_ATTR)); return builder.build(); } private Group parseGroup(final Element element) { final Group.Builder builder = new Group.Builder().identifier(element.getAttribute(IDENTIFIER_ATTR)) .name(element.getAttribute(NAME_ATTR)); NodeList groupUsers = element.getElementsByTagName(GROUP_USER_ELEMENT); for (int i = 0; i < groupUsers.getLength(); i++) { Element groupUserNode = (Element) groupUsers.item(i); builder.addUser(groupUserNode.getAttribute(IDENTIFIER_ATTR)); } return builder.build(); } private void writeUser(final XMLStreamWriter writer, final User user) throws XMLStreamException { writer.writeStartElement(USER_ELEMENT); writer.writeAttribute(IDENTIFIER_ATTR, user.getIdentifier()); writer.writeAttribute(IDENTITY_ATTR, user.getIdentity()); writer.writeEndElement(); } private void writeGroup(final XMLStreamWriter writer, final Group group) throws XMLStreamException { List<String> users = new ArrayList<>(group.getUsers()); Collections.sort(users); writer.writeStartElement(GROUP_ELEMENT); writer.writeAttribute(IDENTIFIER_ATTR, group.getIdentifier()); writer.writeAttribute(NAME_ATTR, group.getName()); for (String user : users) { writer.writeStartElement(GROUP_USER_ELEMENT); writer.writeAttribute(IDENTIFIER_ATTR, user); writer.writeEndElement(); } writer.writeEndElement(); } private org.apache.nifi.registry.security.authorization.file.tenants.generated.User createJAXBUser(User user) { final org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser = new org.apache.nifi.registry.security.authorization.file.tenants.generated.User(); jaxbUser.setIdentifier(user.getIdentifier()); jaxbUser.setIdentity(user.getIdentity()); return jaxbUser; } /** * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up. * * @throws JAXBException Unable to reload the authorized users file * @throws IllegalStateException Unable to sync file with restore * @throws SAXException Unable to unmarshall tenants */ private synchronized void load() throws JAXBException, IllegalStateException, SAXException { final Tenants tenants = unmarshallTenants(); if (tenants.getUsers() == null) { tenants.setUsers(new Users()); } if (tenants.getGroups() == null) { tenants.setGroups(new Groups()); } final UserGroupHolder userGroupHolder = new UserGroupHolder(tenants); final boolean emptyTenants = userGroupHolder.getAllUsers().isEmpty() && userGroupHolder.getAllGroups().isEmpty(); if (emptyTenants) { populateInitialUsers(tenants); // save any changes that were made and repopulate the holder saveAndRefreshHolder(tenants); } else { this.userGroupHolder.set(userGroupHolder); } } private void saveTenants(final Tenants tenants) throws JAXBException { final Marshaller marshaller = JAXB_TENANTS_CONTEXT.createMarshaller(); marshaller.setSchema(tenantsSchema); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(tenants, tenantsFile); } private Tenants unmarshallTenants() throws JAXBException { final Unmarshaller unmarshaller = JAXB_TENANTS_CONTEXT.createUnmarshaller(); unmarshaller.setSchema(tenantsSchema); final JAXBElement<Tenants> element = unmarshaller.unmarshal(new StreamSource(tenantsFile), Tenants.class); return element.getValue(); } private void populateInitialUsers(final Tenants tenants) { for (String initialUserIdentity : initialUserIdentities) { getOrCreateUser(tenants, initialUserIdentity); } } /** * Finds the User with the given identity, or creates a new one and adds it to the Tenants. * * @param tenants the Tenants reference * @param userIdentity the user identity to find or create * @return the User from Tenants with the given identity, or a new instance that was added to Tenants */ private org.apache.nifi.registry.security.authorization.file.tenants.generated.User getOrCreateUser( final Tenants tenants, final String userIdentity) { if (StringUtils.isBlank(userIdentity)) { return null; } org.apache.nifi.registry.security.authorization.file.tenants.generated.User foundUser = null; for (org.apache.nifi.registry.security.authorization.file.tenants.generated.User user : tenants.getUsers() .getUser()) { if (user.getIdentity().equals(userIdentity)) { foundUser = user; break; } } if (foundUser == null) { final String userIdentifier = IdentifierUtil.getIdentifier(userIdentity); foundUser = new org.apache.nifi.registry.security.authorization.file.tenants.generated.User(); foundUser.setIdentifier(userIdentifier); foundUser.setIdentity(userIdentity); tenants.getUsers().getUser().add(foundUser); } return foundUser; } /** * Finds the Group with the given name, or creates a new one and adds it to Tenants. * * @param tenants the Tenants reference * @param groupName the name of the group to look for * @return the Group from Tenants with the given name, or a new instance that was added to Tenants */ private org.apache.nifi.registry.security.authorization.file.tenants.generated.Group getOrCreateGroup( final Tenants tenants, final String groupName) { if (StringUtils.isBlank(groupName)) { return null; } org.apache.nifi.registry.security.authorization.file.tenants.generated.Group foundGroup = null; for (org.apache.nifi.registry.security.authorization.file.tenants.generated.Group group : tenants .getGroups().getGroup()) { if (group.getName().equals(groupName)) { foundGroup = group; break; } } if (foundGroup == null) { final String newGroupIdentifier = IdentifierUtil.getIdentifier(groupName); foundGroup = new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group(); foundGroup.setIdentifier(newGroupIdentifier); foundGroup.setName(groupName); tenants.getGroups().getGroup().add(foundGroup); } return foundGroup; } /** * Saves the Authorizations instance by marshalling to a file, then re-populates the * in-memory data structures and sets the new holder. * * Synchronized to ensure only one thread writes the file at a time. * * @param tenants the tenants to save and populate from * @throws AuthorizationAccessException if an error occurs saving the authorizations */ private synchronized void saveAndRefreshHolder(final Tenants tenants) throws AuthorizationAccessException { try { saveTenants(tenants); this.userGroupHolder.set(new UserGroupHolder(tenants)); } catch (JAXBException e) { throw new AuthorizationAccessException("Unable to save Authorizations", e); } } @Override public void preDestruction() throws SecurityProviderDestructionException { } private static class UsersAndGroups { final List<User> users; final List<Group> groups; public UsersAndGroups(List<User> users, List<Group> groups) { this.users = users; this.groups = groups; } public List<User> getUsers() { return users; } public List<Group> getGroups() { return groups; } } }