org.graylog2.users.RoleServiceImpl Maven / Gradle / Ivy
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* .
*/
package org.graylog2.users;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mongodb.BasicDBObject;
import com.mongodb.DuplicateKeyException;
import org.bson.types.ObjectId;
import org.graylog2.bindings.providers.MongoJackObjectMapperProvider;
import org.graylog2.database.MongoConnection;
import org.graylog2.database.MongoDBUpsertRetryer;
import org.graylog2.database.NotFoundException;
import org.graylog2.plugin.database.ValidationException;
import org.graylog2.shared.security.Permissions;
import org.graylog2.shared.users.Role;
import org.graylog2.shared.users.Roles;
import org.mongojack.DBCursor;
import org.mongojack.DBQuery;
import org.mongojack.JacksonDBCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.mongojack.DBQuery.and;
import static org.mongojack.DBQuery.is;
public class RoleServiceImpl implements RoleService {
private static final Logger log = LoggerFactory.getLogger(RoleServiceImpl.class);
private static final String ROLES = "roles";
private static final String NAME_LOWER = "name_lower";
private static final String READ_ONLY = "read_only";
private static final String ID = "_id";
public static final String ADMIN_ROLENAME = "Admin";
private static final String READER_ROLENAME = "Reader";
private final JacksonDBCollection dbCollection;
private final Validator validator;
private final String adminRoleObjectId;
private final String readerRoleObjectId;
@Inject
public RoleServiceImpl(MongoConnection mongoConnection,
MongoJackObjectMapperProvider mapper,
Permissions permissions,
Validator validator) {
this.validator = validator;
dbCollection = JacksonDBCollection.wrap(
mongoConnection.getDatabase().getCollection(ROLES),
RoleImpl.class,
ObjectId.class,
mapper.get());
// lower case role names are unique, this allows arbitrary naming, but still uses an index
dbCollection.createIndex(new BasicDBObject(NAME_LOWER, 1), new BasicDBObject("unique", true));
// make sure the two built-in roles actually exist
adminRoleObjectId = checkNotNull(ensureBuiltinRole(ADMIN_ROLENAME, Sets.newHashSet("*"), "Admin",
"Grants all permissions for Graylog administrators (built-in)"));
readerRoleObjectId = checkNotNull(ensureBuiltinRole(READER_ROLENAME, permissions.readerBasePermissions(), "Reader",
"Grants basic permissions for every Graylog user (built-in)"));
}
@Nullable
private String ensureBuiltinRole(String roleName,
Set expectedPermissions,
String name, String description) {
RoleImpl previousRole = null;
try {
previousRole = load(roleName);
if (!previousRole.isReadOnly() || !expectedPermissions.equals(previousRole.getPermissions())) {
final String msg = "Invalid role '" + roleName + "', fixing it.";
log.error(msg);
throw new IllegalArgumentException(msg); // jump to fix code
}
} catch (NotFoundException | IllegalArgumentException | NoSuchElementException ignored) {
log.info("{} role is missing or invalid, re-adding it as a built-in role.", roleName);
final RoleImpl fixedAdmin = new RoleImpl();
// copy the mongodb id over, in order to update the role instead of readding it
if (previousRole != null) {
fixedAdmin._id = previousRole._id;
}
fixedAdmin.setReadOnly(true);
fixedAdmin.setName(name);
fixedAdmin.setDescription(description);
fixedAdmin.setPermissions(expectedPermissions);
try {
final RoleImpl savedRole = save(fixedAdmin);
return savedRole.getId();
} catch (DuplicateKeyException | ValidationException e) {
log.error("Unable to save fixed " + roleName + " role, please restart Graylog to fix this.", e);
}
}
if (previousRole == null) {
log.error("Unable to access fixed " + roleName + " role, please restart Graylog to fix this.");
return null;
}
return previousRole.getId();
}
@Override
public Role loadById(String roleId) throws NotFoundException {
final Role role = dbCollection.findOneById(new ObjectId(roleId));
if (role == null) {
throw new NotFoundException("No role found with id " + roleId);
}
return role;
}
@Override
public RoleImpl load(String roleName) throws NotFoundException {
final RoleImpl role = dbCollection.findOne(is(NAME_LOWER, roleName.toLowerCase(Locale.ENGLISH)));
if (role == null) {
throw new NotFoundException("No role found with name " + roleName);
}
return role;
}
@Override
public boolean exists(String roleName) {
return dbCollection.getCount(is(NAME_LOWER, roleName.toLowerCase(Locale.ENGLISH))) == 1;
}
@Override
public Set loadAll() {
try (DBCursor rolesCursor = dbCollection.find()) {
return ImmutableSet.copyOf((Iterable extends Role>) rolesCursor);
}
}
@Override
public Map findIdMap(Set roleIds) throws NotFoundException {
final DBQuery.Query query = DBQuery.in(ID, roleIds);
try (DBCursor rolesCursor = dbCollection.find(query)) {
ImmutableSet roles = ImmutableSet.copyOf((Iterable extends Role>) rolesCursor);
return Maps.uniqueIndex(roles, new Function() {
@Nullable
@Override
public String apply(Role input) {
return input.getId();
}
});
}
}
@Override
public Map loadAllIdMap() throws NotFoundException {
final Set roles = loadAll();
return Maps.uniqueIndex(roles, new Function() {
@Nullable
@Override
public String apply(Role input) {
return input.getId();
}
});
}
@Override
public Map loadAllLowercaseNameMap() throws NotFoundException {
final Set roles = loadAll();
return Maps.uniqueIndex(roles, Roles.roleToNameFunction(true));
}
@Override
public RoleImpl save(Role role1) throws ValidationException {
// sucky but necessary because of graylog2-shared not knowing about mongodb :(
if (!(role1 instanceof RoleImpl)) {
throw new IllegalArgumentException("invalid Role implementation class");
}
RoleImpl role = (RoleImpl) role1;
final Set> violations = validate(role);
if (!violations.isEmpty()) {
throw new ValidationException("Validation failed.", violations.toString());
}
return MongoDBUpsertRetryer.run(() ->
dbCollection.findAndModify(is(NAME_LOWER, role.nameLower()), null, null, false, role, true, true));
}
@Override
public Set> validate(Role role) {
return validator.validate(role);
}
@Override
public int delete(String roleName) {
final DBQuery.Query nameMatchesAndNotReadonly = and(is(READ_ONLY, false), is(NAME_LOWER, roleName.toLowerCase(Locale.ENGLISH)));
return dbCollection.remove(nameMatchesAndNotReadonly).getN();
}
@Override
public String getAdminRoleObjectId() {
return adminRoleObjectId;
}
@Override
public String getReaderRoleObjectId() {
return readerRoleObjectId;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy