/*
* 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 ro.nextreports.server.service;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.protocol.HTTP;
import org.apache.wicket.util.encoding.UrlEncoder;
import org.jasypt.digest.StringDigester;
import org.jasypt.encryption.StringEncryptor;
import org.jfree.util.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.annotation.Transactional;
import ro.nextreports.server.StorageConstants;
import ro.nextreports.server.audit.AuditEvent;
import ro.nextreports.server.audit.Auditor;
import ro.nextreports.server.dao.SecurityDao;
import ro.nextreports.server.dao.StorageDao;
import ro.nextreports.server.domain.AclEntry;
import ro.nextreports.server.domain.Entity;
import ro.nextreports.server.domain.Group;
import ro.nextreports.server.domain.User;
import ro.nextreports.server.exception.NotFoundException;
import ro.nextreports.server.security.Profile;
import ro.nextreports.server.util.Pair;
import ro.nextreports.server.util.PermissionUtil;
import ro.nextreports.server.util.ServerUtil;
/**
* @author Decebal Suiu
*/
public class DefaultSecurityService implements SecurityService {
private static final Logger LOG = LoggerFactory.getLogger(DefaultSecurityService.class);
private static final String SEPARATOR = "-sep-";
private SecurityDao securityDao;
private StorageDao storageDao;
private List<Profile> profiles;
private Auditor auditor;
private StringEncryptor tokenEncryptor;
private StringDigester simpleDigester;
@Required
public void setSecurityDao(SecurityDao securityDao) {
this.securityDao = securityDao;
}
@Required
public void setStorageDao(StorageDao storageDao) {
this.storageDao = storageDao;
}
@Required
public void setProfiles(List<Profile> profiles) {
this.profiles = profiles;
}
@Required
public void setAuditor(Auditor auditor) {
this.auditor = auditor;
}
@Required
public void setTokenEncryptor(StringEncryptor tokenEncryptor) {
this.tokenEncryptor = tokenEncryptor;
}
public void setSimpleDigester(StringDigester simpleDigester) {
this.simpleDigester = simpleDigester;
}
@Transactional(readOnly = true)
public User[] getUsers() {
return securityDao.getUsers();
}
@Transactional(readOnly = true)
public User getUserByName(String username) throws NotFoundException {
return securityDao.getUserByName(username);
}
@Transactional(readOnly = true)
public Group[] getGroups() {
return securityDao.getGroups();
}
@Transactional(readOnly = true)
public Group getGroupByName(String groupname) throws NotFoundException {
return securityDao.getGroupByName(groupname);
}
@Transactional(readOnly = true)
public AclEntry[] getGranted(String entityPath) {
return securityDao.getGranted(entityPath);
}
@Transactional(readOnly = true)
public AclEntry[] getGrantedById(String entityId) {
return securityDao.getGrantedById(entityId);
}
@Transactional(readOnly = true)
public AclEntry[] getGrantedUsers(String entityPath) {
return securityDao.getGrantedUsers(entityPath);
}
@Transactional(readOnly = true)
public AclEntry[] getGrantedUsersById(String entityId) {
return securityDao.getGrantedUsersById(entityId);
}
@Transactional
public void grantUser(String entityPath, String username, int permissions, boolean recursive) throws NotFoundException {
securityDao.grantUser(entityPath, username, permissions, recursive);
AuditEvent auditEvent = new AuditEvent("Grant user");
auditEvent.getContext().put("PATH", entityPath);
auditEvent.getContext().put("USERNAME", username);
auditEvent.getContext().put("PERMISSIONS", PermissionUtil.toString(permissions));
auditEvent.getContext().put("RECURSIVE", recursive);
auditor.logEvent(auditEvent);
}
@Transactional
public void revokeUser(String entityPath, String username, int permissions) {
securityDao.revokeUser(entityPath, username, permissions);
AuditEvent auditEvent = new AuditEvent("Revoke user");
auditEvent.getContext().put("PATH", entityPath);
auditEvent.getContext().put("USERNAME", username);
auditEvent.getContext().put("PERMISSIONS", PermissionUtil.toString(permissions));
auditor.logEvent(auditEvent);
}
@Transactional(readOnly = true)
public AclEntry[] getGrantedGroups(String entityPath) {
return securityDao.getGrantedGroups(entityPath);
}
@Transactional
public void grantGroup(String entityPath, String groupname, int permissions, boolean recursive) throws NotFoundException {
securityDao.grantGroup(entityPath, groupname, permissions, recursive);
AuditEvent auditEvent = new AuditEvent("Grant group");
auditEvent.getContext().put("PATH", entityPath);
auditEvent.getContext().put("GROUPNAME", groupname);
auditEvent.getContext().put("PERMISSIONS", PermissionUtil.toString(permissions));
auditEvent.getContext().put("RECURSIVE", recursive);
auditor.logEvent(auditEvent);
}
@Transactional
public void revokeGroup(String entityPath, String groupname, int permissions) {
securityDao.revokeGroup(entityPath, groupname, permissions);
AuditEvent auditEvent = new AuditEvent("Revoke group");
auditEvent.getContext().put("PATH", entityPath);
auditEvent.getContext().put("GROUPNAME", groupname);
auditEvent.getContext().put("PERMISSIONS", PermissionUtil.toString(permissions));
auditor.logEvent(auditEvent);
}
@Transactional(readOnly = true)
public boolean hasPermissionsById(String userName, int permissions, String entityId) throws NotFoundException {
if (storageDao.isSystemEntity(entityId)) {
return true;
}
User user = getUserByName(userName);
if (user.isAdmin()) {
// admins without realms will see anything
// admins with realms will see outside their realm only if they will have rights
if ("".equals(ServerUtil.getRealm())) {
return true;
} else {
if (storageDao.isEntityFromLoggedRealm(entityId)) {
return true;
}
}
}
AclEntry[] aclEntries = getGrantedById(entityId);
if (aclEntries.length == 0) {
return false;
}
for (AclEntry aclEntry : aclEntries) {
if ((aclEntry.getType() == AclEntry.USER_TYPE) && aclEntry.getName().equals(userName)) {
if ((aclEntry.getPermissions() & permissions) == permissions) {
return true;
} else {
break;
}
}
if (aclEntry.getType() == AclEntry.GROUP_TYPE) {
try {
Group group = getGroupByName(aclEntry.getName());
if (StorageConstants.ALL_GROUP_NAME.equals(group.getName()) || group.isMember(userName)) {
if ((aclEntry.getPermissions() & permissions) == permissions) {
return true;
}
}
} catch (NotFoundException ex) {
// group was removed from security, but it was not removed from entity permissions
Entity e = storageDao.getEntityById(entityId);
LOG.warn(" --> entity with path : '" + e.getPath() + "' has phantom permission for group '" + aclEntry.getName() + "'. You should remove this group permission.");
}
}
}
return false;
}
public List<String> getProfileNames() {
List<String> names = new ArrayList<String>();
if (profiles != null) {
for (Profile p : profiles) {
names.add(p.getName());
}
}
return names;
}
public Profile getProfileByName(String profileName) {
for (Profile p : profiles) {
if (p.getName().equals(profileName)) {
return p;
}
}
return null;
}
/**
* A reset token looks like:
* USERNAME-sep-DIGEST(USER_PASSWORD_HASH)-sep-currentTimeMillis
*/
public String generateResetToken(User user) {
String encryptedToken = tokenEncryptor.encrypt(user.getUsername() + SEPARATOR
+ simpleDigester.digest(user.getPassword()) + SEPARATOR
+ System.currentTimeMillis());
return UrlEncoder.QUERY_INSTANCE.encode(encryptedToken, HTTP.ISO_8859_1);
}
/**
* Returns
* @param encryptedToken
* @return Pair<Username, DigestedPassword>
* @throws RuntimeException - "Malformed Token", "Token Expired" throws exception
*/
public Pair<String, String> decryptResetToken(String encryptedToken) throws RuntimeException {
// USER_ID-sep-DIGESTED_HASH-sep-System.currentTimeMillis
String decriptedMessage = tokenEncryptor.decrypt(encryptedToken);
String[] decriptArray = decriptedMessage.split(SEPARATOR);
if (decriptArray.length != 3) {
throw new RuntimeException("Malformed Token");
}
if (!isTokenUnexpired(decriptArray[2])) {
throw new RuntimeException("Token Expired");
}
String username = decriptArray[0];
String digestedPassword = decriptArray[1];
return new Pair<String, String>(username, digestedPassword);
}
private boolean isTokenUnexpired(String agoSystemMillis) {
long ago = Long.valueOf(agoSystemMillis);
int diffSeconds = (int) (System.currentTimeMillis() - ago) / 1000;
int hours = diffSeconds / 3600;
return hours <= 4;
}
}