blob: cefea74a542a438275387550a9269199d03cfae4 [file] [log] [blame]
/*
* 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.ace.useradmin.repository;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.ace.repository.ext.CachedRepository;
import org.apache.ace.repository.ext.impl.CachedRepositoryImpl;
import org.apache.ace.repository.ext.impl.FilebasedBackupRepository;
import org.apache.ace.repository.ext.impl.RemoteRepository;
import org.apache.ace.useradmin.repository.xstream.GroupDTO;
import org.apache.ace.useradmin.repository.xstream.RoleDTO;
import org.apache.ace.useradmin.repository.xstream.UserDTO;
import org.apache.ace.useradmin.repository.xstream.XStreamFactory;
import org.apache.felix.useradmin.RoleFactory;
import org.apache.felix.useradmin.RoleRepositoryStore;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.log.LogService;
import org.osgi.service.useradmin.Group;
import org.osgi.service.useradmin.Role;
import org.osgi.service.useradmin.User;
import org.osgi.service.useradmin.UserAdminEvent;
import org.osgi.service.useradmin.UserAdminListener;
import com.thoughtworks.xstream.XStream;
/**
* Felix UserAdmin RoleRepositoryStore implementation that's backed by an ACE Repository
*
*/
public class RepositoryBasedRoleRepositoryStore implements RoleRepositoryStore, UserAdminListener {
private volatile BundleContext m_BundleContext;
private volatile LogService m_log;
private volatile RemoteRepository m_repository;
private volatile CachedRepository m_cachedRepository;
private volatile AtomicLong m_version;
private final Map<String, Role> m_roleMap = new ConcurrentHashMap<>();
@SuppressWarnings("unused" /* dependency manager callback */)
private void start() throws IOException {
File currentFile = m_BundleContext.getDataFile("current.xml");
File backupFile = m_BundleContext.getDataFile("backup.xml");
if (currentFile.exists()) {
currentFile.delete();
}
if (backupFile.exists()) {
backupFile.delete();
}
FilebasedBackupRepository backupRepo = new FilebasedBackupRepository(currentFile, backupFile);
m_cachedRepository = new CachedRepositoryImpl(m_repository, backupRepo, CachedRepositoryImpl.UNCOMMITTED_VERSION);
}
private void refreshRoleMap() throws Exception {
m_roleMap.clear();
XStream instance = XStreamFactory.getInstance();
try (InputStream inputStream = m_cachedRepository.checkout(true);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
ObjectInputStream objectInputStream = instance.createObjectInputStream(inputStreamReader)){
RoleDTO roleDto;
List<RoleDTO> rolesWithMemberships = new ArrayList<>();
m_version = new AtomicLong(m_cachedRepository.getMostRecentVersion());
try {
while ((roleDto = (RoleDTO) objectInputStream.readObject()) != null) {
User role;
if (roleDto.type == Role.USER) {
role = RoleFactory.createUser(roleDto.name);
} else if (roleDto.type == Role.GROUP) {
role = RoleFactory.createGroup(roleDto.name);
} else {
throw new IllegalStateException("");
}
if (roleDto.properties != null){
for (Entry<Object, Object> entry : roleDto.properties.entrySet()) {
role.getProperties().put(entry.getKey(), entry.getValue());
}
}
if (roleDto.credentials != null){
for (Entry<Object, Object> entry : roleDto.credentials.entrySet()) {
role.getCredentials().put(entry.getKey(), entry.getValue());
}
}
if (roleDto.memberOf != null && !roleDto.memberOf.isEmpty()){
rolesWithMemberships.add(roleDto);
}
m_roleMap.put(role.getName(), role);
}
}catch (EOFException e) {
// Ignore, this is the way XStream let's us know we're done reading
}
for (RoleDTO role : rolesWithMemberships) {
Role memberRole = m_roleMap.get(role.name);
for (String memberOf : role.memberOf) {
Role groupRole = m_roleMap.get(memberOf);
if (groupRole == null){
throw new IllegalStateException("Target group not found");
}
if (groupRole.getType() != Role.GROUP) {
throw new IllegalStateException("Target is not a group");
}
Group group = (Group) groupRole;
group.addMember(memberRole);
}
}
// Wrap users and groups in repository user / group types
for (Entry<String, Role> roleMapEntry : m_roleMap.entrySet()) {
m_roleMap.put(roleMapEntry.getKey(), wrapRole(roleMapEntry.getValue()));
}
}
}
/**
* Add a wrapper around a Role that prevents changes to Users / Groups when the repository is out of sync
*
* @param role User or Group role to be wrapped
* @return a wrapped Role
*/
private Role wrapRole(Role role) {
if (role.getType() == Role.USER) {
return new RepositoryUser((User)role, m_cachedRepository, m_version);
} else if (role.getType() == Role.GROUP) {
return new RepositoryGroup((Group)role, m_cachedRepository, m_version);
}else {
throw new IllegalStateException("");
}
}
@Override
public Role getRoleByName(String name) throws Exception {
if (name == null) {
return null;
}
synchronized (m_roleMap) {
if (!m_cachedRepository.isCurrent()) {
refreshRoleMap();
}
return m_roleMap.get(name);
}
}
@Override
public Role[] getRoles(String filterString) throws Exception {
synchronized (m_roleMap) {
if (!m_cachedRepository.isCurrent()) {
refreshRoleMap();
}
if (filterString == null) {
return m_roleMap.values().toArray(new Role[0]);
}
Filter filter = FrameworkUtil.createFilter(filterString);
List<Role> matchingRoles = new ArrayList<>();
for (Role role: m_roleMap.values()){
if (filter.match(role.getProperties())){
matchingRoles.add(role);
}
}
return matchingRoles.toArray(new Role[matchingRoles.size()]);
}
}
@Override
public Role addRole(String name, int type) throws Exception {
Role role;
switch (type) {
case Role.USER:
role = RoleFactory.createUser(name);
break;
case Role.GROUP:
role = RoleFactory.createGroup(name);
break;
default:
throw new IllegalArgumentException("Invalid group type " + type);
}
synchronized (m_roleMap) {
if (m_cachedRepository.getMostRecentVersion() == -1) {
refreshRoleMap();
}
if (m_roleMap.containsKey(name)){
return null;
}
role = wrapRole(role);
m_roleMap.put(name, role);
roleChanged(null);
}
return role;
}
@Override
public Role removeRole(String name) throws Exception {
Role removedRole;
synchronized (m_roleMap) {
removedRole = m_roleMap.remove(name);
if (removedRole != null){
roleChanged(null);
}
}
return removedRole;
}
List<String> memberOf(Role role) {
List<String> memberOf = new ArrayList<>();
for (Role r: m_roleMap.values()) {
if (r instanceof Group) {
Group group = (Group) r;
Role[] members = group.getMembers();
if (members != null) {
if (contains(role, members)) {
memberOf.add(group.getName());
}
}
}
}
return memberOf;
}
/**
* Helper method that checks the presence of an object in an array. Returns <code>true</code> if <code>t</code> is
* in <code>ts</code>, <code>false</code> otherwise.
*/
private <T> boolean contains(T t, T[] ts) {
for (T current : ts) {
if (current.equals(t)) {
return true;
}
}
return false;
}
@Override
public void roleChanged(UserAdminEvent event) {
synchronized (m_roleMap) {
XStream instance = XStreamFactory.getInstance();
try (StringWriter writer = new StringWriter();
ObjectOutputStream stream =
instance.createObjectOutputStream(writer, "roles");) {
for (Role role : m_roleMap.values()) {
List<String> memberOf = memberOf(role);
if (role.getType() == Role.USER) {
stream.writeObject(new UserDTO((User) role, memberOf));
} else if (role.getType() == Role.GROUP) {
GroupDTO obj = new GroupDTO((Group) role, memberOf);
stream.writeObject(obj);
} else {
throw new IllegalStateException("Unsupported role type");
}
}
stream.flush();
stream.close();
writer.flush();
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(writer.toString().getBytes())){
m_cachedRepository.writeLocal(inputStream);
}
m_cachedRepository.commit();
m_version.set(m_cachedRepository.getMostRecentVersion());
} catch (IOException e) {
m_log.log(LogService.LOG_ERROR, "Failed to commit role changes to the main role repository", e);
}
}
}
}