changed the User API

- Now it's more aligned with other APIs in ES (e.g. index template API)
- the "get user" API now returns an object as a response. The users are keyed by their username. If none of the requested users is found, an empty object will be returned with a 404 response status.
- the body of "put user" request doesn't require "username" anymore (as it's defined as part of the URL)

Original commit: elastic/x-pack-elasticsearch@f7c12648b1
This commit is contained in:
uboness 2016-02-28 03:47:28 +01:00 committed by jaymode
parent 1f113e07f4
commit 759d99de9c
14 changed files with 126 additions and 116 deletions

View File

@ -34,8 +34,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/ */
public class ShieldTemplateService extends AbstractComponent implements ClusterStateListener { public class ShieldTemplateService extends AbstractComponent implements ClusterStateListener {
public static final String SHIELD_ADMIN_INDEX_NAME = ".security"; public static final String SECURITY_INDEX_NAME = ".security";
public static final String SHIELD_TEMPLATE_NAME = "security-index-template"; public static final String SECURITY_TEMPLATE_NAME = "security-index-template";
private final ThreadPool threadPool; private final ThreadPool threadPool;
private final Provider<InternalClient> clientProvider; private final Provider<InternalClient> clientProvider;
@ -52,22 +52,22 @@ public class ShieldTemplateService extends AbstractComponent implements ClusterS
private void createShieldTemplate() { private void createShieldTemplate() {
final Client client = clientProvider.get(); final Client client = clientProvider.get();
try (InputStream is = getClass().getResourceAsStream("/" + SHIELD_TEMPLATE_NAME + ".json")) { try (InputStream is = getClass().getResourceAsStream("/" + SECURITY_TEMPLATE_NAME + ".json")) {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
Streams.copy(is, out); Streams.copy(is, out);
final byte[] template = out.toByteArray(); final byte[] template = out.toByteArray();
logger.info("--> putting the shield index template"); logger.info("--> putting the shield index template");
PutIndexTemplateRequest putTemplateRequest = client.admin().indices() PutIndexTemplateRequest putTemplateRequest = client.admin().indices()
.preparePutTemplate(SHIELD_TEMPLATE_NAME).setSource(template).request(); .preparePutTemplate(SECURITY_TEMPLATE_NAME).setSource(template).request();
PutIndexTemplateResponse templateResponse = client.admin().indices().putTemplate(putTemplateRequest).get(); PutIndexTemplateResponse templateResponse = client.admin().indices().putTemplate(putTemplateRequest).get();
if (templateResponse.isAcknowledged() == false) { if (templateResponse.isAcknowledged() == false) {
throw new ElasticsearchException("adding template for shield admin index was not acknowledged"); throw new ElasticsearchException("adding template for shield admin index was not acknowledged");
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("failed to create shield admin index template [{}]", logger.error("failed to create shield admin index template [{}]",
e, SHIELD_ADMIN_INDEX_NAME); e, SECURITY_INDEX_NAME);
throw new IllegalStateException("failed to create shield admin index template [" + throw new IllegalStateException("failed to create shield admin index template [" +
SHIELD_ADMIN_INDEX_NAME + "]", e); SECURITY_INDEX_NAME + "]", e);
} }
} }
@ -80,13 +80,13 @@ public class ShieldTemplateService extends AbstractComponent implements ClusterS
return; return;
} }
IndexRoutingTable shieldIndexRouting = event.state().routingTable().index(SHIELD_ADMIN_INDEX_NAME); IndexRoutingTable shieldIndexRouting = event.state().routingTable().index(SECURITY_INDEX_NAME);
if (shieldIndexRouting == null) { if (shieldIndexRouting == null) {
if (event.localNodeMaster()) { if (event.localNodeMaster()) {
ClusterState state = event.state(); ClusterState state = event.state();
// TODO for the future need to add some checking in the event the template needs to be updated... // TODO for the future need to add some checking in the event the template needs to be updated...
IndexTemplateMetaData templateMeta = state.metaData().templates().get(SHIELD_TEMPLATE_NAME); IndexTemplateMetaData templateMeta = state.metaData().templates().get(SECURITY_TEMPLATE_NAME);
final boolean createTemplate = (templateMeta == null); final boolean createTemplate = (templateMeta == null);
if (createTemplate && templateCreationPending.compareAndSet(false, true)) { if (createTemplate && templateCreationPending.compareAndSet(false, true)) {

View File

@ -158,13 +158,12 @@ public class User implements ToXContent {
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(Fields.USERNAME.getPreferredName(), principal()); builder.field(Fields.USERNAME.getPreferredName(), username);
builder.array(Fields.ROLES.getPreferredName(), roles()); builder.array(Fields.ROLES.getPreferredName(), roles);
builder.field(Fields.FULL_NAME.getPreferredName(), fullName); builder.field(Fields.FULL_NAME.getPreferredName(), fullName);
builder.field(Fields.EMAIL.getPreferredName(), email); builder.field(Fields.EMAIL.getPreferredName(), email);
builder.field(Fields.METADATA.getPreferredName(), metadata); builder.field(Fields.METADATA.getPreferredName(), metadata);
builder.endObject(); return builder.endObject();
return builder;
} }
public static User readFrom(StreamInput input) throws IOException { public static User readFrom(StreamInput input) throws IOException {

View File

@ -43,7 +43,7 @@ public class XPackUser extends User {
// these will be the index permissions required by shield (will uncomment once we optimize watcher permissions) // these will be the index permissions required by shield (will uncomment once we optimize watcher permissions)
// .add(IndexPrivilege.ALL, ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME) // .add(IndexPrivilege.ALL, ShieldTemplateService.SECURITY_INDEX_NAME)
// .add(IndexPrivilege.ALL, IndexAuditTrail.INDEX_NAME_PREFIX + "*") // .add(IndexPrivilege.ALL, IndexAuditTrail.INDEX_NAME_PREFIX + "*")

View File

@ -64,7 +64,8 @@ public class PutUserRequestBuilder extends ActionRequestBuilder<PutUserRequest,
return this; return this;
} }
public PutUserRequestBuilder source(BytesReference source) throws IOException { public PutUserRequestBuilder source(String username, BytesReference source) throws IOException {
username(username);
try (XContentParser parser = XContentHelper.createParser(source)) { try (XContentParser parser = XContentHelper.createParser(source)) {
XContentUtils.verifyObject(parser); XContentUtils.verifyObject(parser);
XContentParser.Token token; XContentParser.Token token;
@ -73,11 +74,9 @@ public class PutUserRequestBuilder extends ActionRequestBuilder<PutUserRequest,
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, User.Fields.USERNAME)) { } else if (ParseFieldMatcher.STRICT.match(currentFieldName, User.Fields.USERNAME)) {
if (token == XContentParser.Token.VALUE_STRING) { if (username.equals(parser.text()) == false) {
username(parser.text()); throw new ElasticsearchParseException("failed to parse user [{}]. username doesn't match user id [{}]",
} else { username, parser.text());
throw new ElasticsearchParseException(
"expected field [{}] to be of type string, but found [{}] instead", currentFieldName, token);
} }
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, User.Fields.PASSWORD)) { } else if (ParseFieldMatcher.STRICT.match(currentFieldName, User.Fields.PASSWORD)) {
if (token == XContentParser.Token.VALUE_STRING) { if (token == XContentParser.Token.VALUE_STRING) {

View File

@ -88,7 +88,7 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
} }
// TODO - perhaps separate indices for users/roles instead of types? // TODO - perhaps separate indices for users/roles instead of types?
public static final String INDEX_USER_TYPE = "user"; public static final String USER_DOC_TYPE = "user";
// this map contains the mapping for username -> version, which is used when polling the index to easily detect of // this map contains the mapping for username -> version, which is used when polling the index to easily detect of
// any changes that may have been missed since the last update. // any changes that may have been missed since the last update.
@ -168,12 +168,13 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
final List<User> users = new ArrayList<>(); final List<User> users = new ArrayList<>();
QueryBuilder query; QueryBuilder query;
if (usernames == null || usernames.length == 0) { if (usernames == null || usernames.length == 0) {
query = QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("_type", INDEX_USER_TYPE)); query = QueryBuilders.matchAllQuery();
} else { } else {
query = QueryBuilders.boolQuery().filter(QueryBuilders.idsQuery(INDEX_USER_TYPE).addIds(usernames)); query = QueryBuilders.boolQuery().filter(QueryBuilders.idsQuery(USER_DOC_TYPE).addIds(usernames));
} }
SearchRequest request = client.prepareSearch(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME) SearchRequest request = client.prepareSearch(ShieldTemplateService.SECURITY_INDEX_NAME)
.setScroll(scrollKeepAlive) .setScroll(scrollKeepAlive)
.setTypes(USER_DOC_TYPE)
.setQuery(query) .setQuery(query)
.setSize(scrollSize) .setSize(scrollSize)
.setFetchSource(true) .setFetchSource(true)
@ -187,7 +188,7 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
boolean hasHits = resp.getHits().getHits().length > 0; boolean hasHits = resp.getHits().getHits().length > 0;
if (hasHits) { if (hasHits) {
for (SearchHit hit : resp.getHits().getHits()) { for (SearchHit hit : resp.getHits().getHits()) {
UserAndPassword u = transformUser(hit.getSource()); UserAndPassword u = transformUser(hit.getId(), hit.getSource());
if (u != null) { if (u != null) {
users.add(u.user()); users.add(u.user());
} }
@ -257,12 +258,12 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
private void getUserAndPassword(String user, final ActionListener<UserAndPassword> listener) { private void getUserAndPassword(String user, final ActionListener<UserAndPassword> listener) {
try { try {
GetRequest request = client.prepareGet(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME, INDEX_USER_TYPE, user).request(); GetRequest request = client.prepareGet(ShieldTemplateService.SECURITY_INDEX_NAME, USER_DOC_TYPE, user).request();
request.indicesOptions().ignoreUnavailable(); request.indicesOptions().ignoreUnavailable();
client.get(request, new ActionListener<GetResponse>() { client.get(request, new ActionListener<GetResponse>() {
@Override @Override
public void onResponse(GetResponse getFields) { public void onResponse(GetResponse response) {
listener.onResponse(transformUser(getFields.getSource())); listener.onResponse(transformUser(response.getId(), response.getSource()));
} }
@Override @Override
@ -286,24 +287,24 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
} }
} }
public void putUser(final PutUserRequest putUserRequest, final ActionListener<Boolean> listener) { public void putUser(final PutUserRequest request, final ActionListener<Boolean> listener) {
if (state() != State.STARTED) { if (state() != State.STARTED) {
listener.onFailure(new IllegalStateException("user cannot be added as native user service has not been started")); listener.onFailure(new IllegalStateException("user cannot be added as native user service has not been started"));
return; return;
} }
try { try {
IndexRequest request = client.prepareIndex(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME, IndexRequest indexRequest = client.prepareIndex(ShieldTemplateService.SECURITY_INDEX_NAME, USER_DOC_TYPE, request.username())
INDEX_USER_TYPE, putUserRequest.username()) // we still index the username for more intuitive searchability (e.g. using queries like "username: joe")
.setSource(User.Fields.USERNAME.getPreferredName(), putUserRequest.username(), .setSource(User.Fields.USERNAME.getPreferredName(), request.username(),
User.Fields.PASSWORD.getPreferredName(), String.valueOf(putUserRequest.passwordHash()), User.Fields.PASSWORD.getPreferredName(), String.valueOf(request.passwordHash()),
User.Fields.ROLES.getPreferredName(), putUserRequest.roles(), User.Fields.ROLES.getPreferredName(), request.roles(),
User.Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(), User.Fields.FULL_NAME.getPreferredName(), request.fullName(),
User.Fields.EMAIL.getPreferredName(), putUserRequest.email(), User.Fields.EMAIL.getPreferredName(), request.email(),
User.Fields.METADATA.getPreferredName(), putUserRequest.metadata()) User.Fields.METADATA.getPreferredName(), request.metadata())
.request(); .request();
client.index(request, new ActionListener<IndexResponse>() { client.index(indexRequest, new ActionListener<IndexResponse>() {
@Override @Override
public void onResponse(IndexResponse indexResponse) { public void onResponse(IndexResponse indexResponse) {
// if the document was just created, then we don't need to clear cache // if the document was just created, then we don't need to clear cache
@ -312,7 +313,7 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
return; return;
} }
clearRealmCache(putUserRequest.username(), listener, indexResponse.isCreated()); clearRealmCache(request.username(), listener, indexResponse.isCreated());
} }
@Override @Override
@ -333,8 +334,8 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
} }
try { try {
DeleteRequest request = client.prepareDelete(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME, DeleteRequest request = client.prepareDelete(ShieldTemplateService.SECURITY_INDEX_NAME,
INDEX_USER_TYPE, deleteUserRequest.username()).request(); USER_DOC_TYPE, deleteUserRequest.username()).request();
request.indicesOptions().ignoreUnavailable(); request.indicesOptions().ignoreUnavailable();
client.delete(request, new ActionListener<DeleteResponse>() { client.delete(request, new ActionListener<DeleteResponse>() {
@Override @Override
@ -366,21 +367,21 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
return false; return false;
} }
if (clusterState.metaData().templates().get(ShieldTemplateService.SHIELD_TEMPLATE_NAME) == null) { if (clusterState.metaData().templates().get(ShieldTemplateService.SECURITY_TEMPLATE_NAME) == null) {
logger.debug("native users template [{}] does not exist, so service cannot start", logger.debug("native users template [{}] does not exist, so service cannot start",
ShieldTemplateService.SHIELD_TEMPLATE_NAME); ShieldTemplateService.SECURITY_TEMPLATE_NAME);
return false; return false;
} }
IndexMetaData metaData = clusterState.metaData().index(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); IndexMetaData metaData = clusterState.metaData().index(ShieldTemplateService.SECURITY_INDEX_NAME);
if (metaData == null) { if (metaData == null) {
logger.debug("shield user index [{}] does not exist, so service can start", ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); logger.debug("shield user index [{}] does not exist, so service can start", ShieldTemplateService.SECURITY_INDEX_NAME);
return true; return true;
} }
if (clusterState.routingTable().index(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME).allPrimaryShardsActive()) { if (clusterState.routingTable().index(ShieldTemplateService.SECURITY_INDEX_NAME).allPrimaryShardsActive()) {
logger.debug("shield user index [{}] all primary shards started, so service can start", logger.debug("shield user index [{}] all primary shards started, so service can start",
ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ShieldTemplateService.SECURITY_INDEX_NAME);
return true; return true;
} }
return false; return false;
@ -472,11 +473,11 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
@Override @Override
public void clusterChanged(ClusterChangedEvent event) { public void clusterChanged(ClusterChangedEvent event) {
final boolean exists = event.state().metaData().indices().get(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME) != null; final boolean exists = event.state().metaData().indices().get(ShieldTemplateService.SECURITY_INDEX_NAME) != null;
// make sure all the primaries are active // make sure all the primaries are active
if (exists && event.state().routingTable().index(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME).allPrimaryShardsActive()) { if (exists && event.state().routingTable().index(ShieldTemplateService.SECURITY_INDEX_NAME).allPrimaryShardsActive()) {
logger.debug("shield user index [{}] all primary shards started, so polling can start", logger.debug("shield user index [{}] all primary shards started, so polling can start",
ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ShieldTemplateService.SECURITY_INDEX_NAME);
shieldIndexExists = true; shieldIndexExists = true;
} else { } else {
// always set the value - it may have changed... // always set the value - it may have changed...
@ -502,12 +503,11 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
} }
@Nullable @Nullable
private UserAndPassword transformUser(Map<String, Object> sourceMap) { private UserAndPassword transformUser(String username, Map<String, Object> sourceMap) {
if (sourceMap == null) { if (sourceMap == null) {
return null; return null;
} }
try { try {
String username = (String) sourceMap.get(User.Fields.USERNAME.getPreferredName());
String password = (String) sourceMap.get(User.Fields.PASSWORD.getPreferredName()); String password = (String) sourceMap.get(User.Fields.PASSWORD.getPreferredName());
String[] roles = ((List<String>) sourceMap.get(User.Fields.ROLES.getPreferredName())).toArray(Strings.EMPTY_ARRAY); String[] roles = ((List<String>) sourceMap.get(User.Fields.ROLES.getPreferredName())).toArray(Strings.EMPTY_ARRAY);
String fullName = (String) sourceMap.get(User.Fields.FULL_NAME.getPreferredName()); String fullName = (String) sourceMap.get(User.Fields.FULL_NAME.getPreferredName());
@ -529,7 +529,7 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
} }
if (shieldIndexExists == false) { if (shieldIndexExists == false) {
logger.trace("cannot poll for user changes since shield admin index [{}] does not exist", ShieldTemplateService logger.trace("cannot poll for user changes since shield admin index [{}] does not exist", ShieldTemplateService
.SHIELD_ADMIN_INDEX_NAME); .SECURITY_INDEX_NAME);
return; return;
} }
@ -609,9 +609,9 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
final ObjectLongMap<String> map = new ObjectLongHashMap<>(); final ObjectLongMap<String> map = new ObjectLongHashMap<>();
SearchResponse response = null; SearchResponse response = null;
try { try {
SearchRequest request = client.prepareSearch(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME) SearchRequest request = client.prepareSearch(ShieldTemplateService.SECURITY_INDEX_NAME)
.setScroll(scrollKeepAlive) .setScroll(scrollKeepAlive)
.setQuery(QueryBuilders.typeQuery(INDEX_USER_TYPE)) .setQuery(QueryBuilders.typeQuery(USER_DOC_TYPE))
.setSize(scrollSize) .setSize(scrollSize)
.setVersion(true) .setVersion(true)
.setFetchSource(true) .setFetchSource(true)

View File

@ -134,7 +134,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
} else { } else {
query = QueryBuilders.boolQuery().filter(QueryBuilders.idsQuery(INDEX_ROLE_TYPE).addIds(rolesToGet)); query = QueryBuilders.boolQuery().filter(QueryBuilders.idsQuery(INDEX_ROLE_TYPE).addIds(rolesToGet));
} }
SearchRequest request = client.prepareSearch(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME) SearchRequest request = client.prepareSearch(ShieldTemplateService.SECURITY_INDEX_NAME)
.setScroll(scrollKeepAlive) .setScroll(scrollKeepAlive)
.setQuery(query) .setQuery(query)
.setSize(scrollSize) .setSize(scrollSize)
@ -205,7 +205,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
private void executeGetRoleRequest(String role, ActionListener<GetResponse> listener) { private void executeGetRoleRequest(String role, ActionListener<GetResponse> listener) {
try { try {
GetRequest request = client.prepareGet(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME, INDEX_ROLE_TYPE, role).request(); GetRequest request = client.prepareGet(ShieldTemplateService.SECURITY_INDEX_NAME, INDEX_ROLE_TYPE, role).request();
request.indicesOptions().ignoreUnavailable(); request.indicesOptions().ignoreUnavailable();
client.get(request, listener); client.get(request, listener);
} catch (Exception e) { } catch (Exception e) {
@ -220,7 +220,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
listener.onResponse(false); listener.onResponse(false);
} }
try { try {
DeleteRequest request = client.prepareDelete(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME, DeleteRequest request = client.prepareDelete(ShieldTemplateService.SECURITY_INDEX_NAME,
INDEX_ROLE_TYPE, deleteRoleRequest.role()).request(); INDEX_ROLE_TYPE, deleteRoleRequest.role()).request();
request.indicesOptions().ignoreUnavailable(); request.indicesOptions().ignoreUnavailable();
client.delete(request, new ActionListener<DeleteResponse>() { client.delete(request, new ActionListener<DeleteResponse>() {
@ -294,7 +294,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
listener.onResponse(false); listener.onResponse(false);
} }
try { try {
IndexRequest request = client.prepareIndex(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME, IndexRequest request = client.prepareIndex(ShieldTemplateService.SECURITY_INDEX_NAME,
INDEX_ROLE_TYPE, putRoleRequest.name()) INDEX_ROLE_TYPE, putRoleRequest.name())
.setSource(putRoleRequest.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)) .setSource(putRoleRequest.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS))
.request(); .request();
@ -340,9 +340,9 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
return false; return false;
} }
if (clusterState.metaData().templates().get(ShieldTemplateService.SHIELD_TEMPLATE_NAME) == null) { if (clusterState.metaData().templates().get(ShieldTemplateService.SECURITY_TEMPLATE_NAME) == null) {
logger.debug("native roles template [{}] does not exist, so service cannot start", logger.debug("native roles template [{}] does not exist, so service cannot start",
ShieldTemplateService.SHIELD_TEMPLATE_NAME); ShieldTemplateService.SECURITY_TEMPLATE_NAME);
return false; return false;
} }
// Okay to start... // Okay to start...
@ -362,7 +362,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
poller.doRun(); poller.doRun();
} catch (Exception e) { } catch (Exception e) {
logger.warn("failed to perform initial poll of roles index [{}]. scheduling again in [{}]", e, logger.warn("failed to perform initial poll of roles index [{}]. scheduling again in [{}]", e,
ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME, pollInterval); ShieldTemplateService.SECURITY_INDEX_NAME, pollInterval);
} }
versionChecker = threadPool.scheduleWithFixedDelay(poller, pollInterval); versionChecker = threadPool.scheduleWithFixedDelay(poller, pollInterval);
state.set(State.STARTED); state.set(State.STARTED);
@ -426,11 +426,11 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
// TODO abstract this code rather than duplicating... // TODO abstract this code rather than duplicating...
@Override @Override
public void clusterChanged(ClusterChangedEvent event) { public void clusterChanged(ClusterChangedEvent event) {
final boolean exists = event.state().metaData().indices().get(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME) != null; final boolean exists = event.state().metaData().indices().get(ShieldTemplateService.SECURITY_INDEX_NAME) != null;
// make sure all the primaries are active // make sure all the primaries are active
if (exists && event.state().routingTable().index(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME).allPrimaryShardsActive()) { if (exists && event.state().routingTable().index(ShieldTemplateService.SECURITY_INDEX_NAME).allPrimaryShardsActive()) {
logger.debug("shield roles index [{}] all primary shards started, so polling can start", logger.debug("shield roles index [{}] all primary shards started, so polling can start",
ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ShieldTemplateService.SECURITY_INDEX_NAME);
shieldIndexExists = true; shieldIndexExists = true;
} else { } else {
// always set the value - it may have changed... // always set the value - it may have changed...
@ -460,7 +460,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
} }
if (shieldIndexExists == false) { if (shieldIndexExists == false) {
logger.trace("cannot poll for role changes since shield admin index [{}] does not exist", logger.trace("cannot poll for role changes since shield admin index [{}] does not exist",
ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ShieldTemplateService.SECURITY_INDEX_NAME);
return; return;
} }
@ -473,7 +473,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
// create a copy of the keys in the cache since we will be modifying this list // create a copy of the keys in the cache since we will be modifying this list
final Set<String> existingRoles = new HashSet<>(roleCache.keySet()); final Set<String> existingRoles = new HashSet<>(roleCache.keySet());
try { try {
SearchRequest request = client.prepareSearch(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME) SearchRequest request = client.prepareSearch(ShieldTemplateService.SECURITY_INDEX_NAME)
.setScroll(scrollKeepAlive) .setScroll(scrollKeepAlive)
.setQuery(QueryBuilders.typeQuery(INDEX_ROLE_TYPE)) .setQuery(QueryBuilders.typeQuery(INDEX_ROLE_TYPE))
.setSize(scrollSize) .setSize(scrollSize)

View File

@ -119,8 +119,8 @@ public class SecurityClient {
/** User Management */ /** User Management */
public GetUsersRequestBuilder prepareGetUsers() { public GetUsersRequestBuilder prepareGetUsers(String... usernames) {
return new GetUsersRequestBuilder(client); return new GetUsersRequestBuilder(client).usernames(usernames);
} }
public void getUsers(GetUsersRequest request, ActionListener<GetUsersResponse> listener) { public void getUsers(GetUsersRequest request, ActionListener<GetUsersResponse> listener) {
@ -136,7 +136,7 @@ public class SecurityClient {
} }
public PutUserRequestBuilder preparePutUser(String username, BytesReference source) throws IOException { public PutUserRequestBuilder preparePutUser(String username, BytesReference source) throws IOException {
return new PutUserRequestBuilder(client).username(username).source(source); return new PutUserRequestBuilder(client).source(username, source);
} }
public PutUserRequestBuilder preparePutUser(String username, char[] password, String... roles) { public PutUserRequestBuilder preparePutUser(String username, char[] password, String... roles) {

View File

@ -39,17 +39,23 @@ public class RestGetUsersAction extends BaseRestHandler {
protected void handleRequest(RestRequest request, final RestChannel channel, Client client) throws Exception { protected void handleRequest(RestRequest request, final RestChannel channel, Client client) throws Exception {
String[] usernames = request.paramAsStringArray("username", Strings.EMPTY_ARRAY); String[] usernames = request.paramAsStringArray("username", Strings.EMPTY_ARRAY);
new SecurityClient(client).prepareGetUsers().usernames(usernames).execute(new RestBuilderListener<GetUsersResponse>(channel) { new SecurityClient(client).prepareGetUsers(usernames).execute(new RestBuilderListener<GetUsersResponse>(channel) {
@Override @Override
public RestResponse buildResponse(GetUsersResponse getUsersResponse, XContentBuilder builder) throws Exception { public RestResponse buildResponse(GetUsersResponse response, XContentBuilder builder) throws Exception {
builder.startObject(); builder.startObject();
builder.field("found", getUsersResponse.hasUsers()); for (User user : response.users()) {
builder.startArray("users"); builder.field(user.principal(), user);
for (User user : getUsersResponse.users()) {
user.toXContent(builder, ToXContent.EMPTY_PARAMS);
} }
builder.endArray();
builder.endObject(); builder.endObject();
// if the user asked for specific users, but none of them were found
// we'll return an empty result and 404 status code
if (usernames.length != 0 && response.users().length == 0) {
return new BytesRestResponse(RestStatus.NOT_FOUND, builder);
}
// either the user asked for all users, or at least one of the users
// was found
return new BytesRestResponse(RestStatus.OK, builder); return new BytesRestResponse(RestStatus.OK, builder);
} }
}); });

View File

@ -88,7 +88,7 @@ public class ClearRolesCacheTests extends ShieldIntegTestCase {
logger.debug("--> created role [{}]", role); logger.debug("--> created role [{}]", role);
} }
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ensureGreen(ShieldTemplateService.SECURITY_INDEX_NAME);
// warm up the caches on every node // warm up the caches on every node
for (ESNativeRolesStore rolesStore : internalCluster().getInstances(ESNativeRolesStore.class)) { for (ESNativeRolesStore rolesStore : internalCluster().getInstances(ESNativeRolesStore.class)) {
@ -134,7 +134,7 @@ public class ClearRolesCacheTests extends ShieldIntegTestCase {
List<String> toModify = randomSubsetOf(modifiedRolesCount, roles); List<String> toModify = randomSubsetOf(modifiedRolesCount, roles);
logger.debug("--> modifying roles {} to have run_as", toModify); logger.debug("--> modifying roles {} to have run_as", toModify);
for (String role : toModify) { for (String role : toModify) {
UpdateResponse response = client.prepareUpdate().setId(role).setIndex(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME) UpdateResponse response = client.prepareUpdate().setId(role).setIndex(ShieldTemplateService.SECURITY_INDEX_NAME)
.setType(ESNativeRolesStore.INDEX_ROLE_TYPE) .setType(ESNativeRolesStore.INDEX_ROLE_TYPE)
.setDoc("run_as", new String[] { role }) .setDoc("run_as", new String[] { role })
.get(); .get();
@ -177,7 +177,7 @@ public class ClearRolesCacheTests extends ShieldIntegTestCase {
List<RoleDescriptor> foundRoles = securityClient.prepareGetRoles().names(role).get().roles(); List<RoleDescriptor> foundRoles = securityClient.prepareGetRoles().names(role).get().roles();
assertThat(foundRoles.size(), is(1)); assertThat(foundRoles.size(), is(1));
logger.debug("--> deleting role [{}]", role); logger.debug("--> deleting role [{}]", role);
DeleteResponse response = client.prepareDelete(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME, DeleteResponse response = client.prepareDelete(ShieldTemplateService.SECURITY_INDEX_NAME,
ESNativeRolesStore.INDEX_ROLE_TYPE, role).get(); ESNativeRolesStore.INDEX_ROLE_TYPE, role).get();
assertThat(response.isFound(), is(true)); assertThat(response.isFound(), is(true));

View File

@ -52,7 +52,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
public void testGettingUserThatDoesntExist() throws Exception { public void testGettingUserThatDoesntExist() throws Exception {
SecurityClient c = securityClient(); SecurityClient c = securityClient();
GetUsersResponse resp = c.prepareGetUsers().usernames("joe").get(); GetUsersResponse resp = c.prepareGetUsers("joe").get();
assertFalse("user should not exist", resp.hasUsers()); assertFalse("user should not exist", resp.hasUsers());
GetRolesResponse resp2 = c.prepareGetRoles().names("role").get(); GetRolesResponse resp2 = c.prepareGetRoles().names("role").get();
assertFalse("role should not exist", resp2.isExists()); assertFalse("role should not exist", resp2.isExists());
@ -63,9 +63,9 @@ public class ESNativeTests extends ShieldIntegTestCase {
logger.error("--> creating user"); logger.error("--> creating user");
c.preparePutUser("joe", "s3kirt".toCharArray(), "role1", "user").get(); c.preparePutUser("joe", "s3kirt".toCharArray(), "role1", "user").get();
logger.error("--> waiting for .shield index"); logger.error("--> waiting for .shield index");
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ensureGreen(ShieldTemplateService.SECURITY_INDEX_NAME);
logger.info("--> retrieving user"); logger.info("--> retrieving user");
GetUsersResponse resp = c.prepareGetUsers().usernames("joe").get(); GetUsersResponse resp = c.prepareGetUsers("joe").get();
assertTrue("user should exist", resp.hasUsers()); assertTrue("user should exist", resp.hasUsers());
User joe = resp.users()[0]; User joe = resp.users()[0];
assertEquals(joe.principal(), "joe"); assertEquals(joe.principal(), "joe");
@ -86,7 +86,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
CollectionUtil.timSort(names); CollectionUtil.timSort(names);
assertArrayEquals(new String[] { "joe", "joe2", "joe3" }, names.toArray(Strings.EMPTY_ARRAY)); assertArrayEquals(new String[] { "joe", "joe2", "joe3" }, names.toArray(Strings.EMPTY_ARRAY));
GetUsersResponse someUsersResp = c.prepareGetUsers().usernames("joe", "joe3").get(); GetUsersResponse someUsersResp = c.prepareGetUsers("joe", "joe3").get();
assertTrue("users should exist", someUsersResp.hasUsers()); assertTrue("users should exist", someUsersResp.hasUsers());
assertEquals("should be 2 users returned", 2, someUsersResp.users().length); assertEquals("should be 2 users returned", 2, someUsersResp.users().length);
names = new ArrayList<>(2); names = new ArrayList<>(2);
@ -100,7 +100,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
DeleteUserResponse delResp = c.prepareDeleteUser("joe").get(); DeleteUserResponse delResp = c.prepareDeleteUser("joe").get();
assertTrue(delResp.found()); assertTrue(delResp.found());
logger.info("--> retrieving user"); logger.info("--> retrieving user");
resp = c.prepareGetUsers().usernames("joe").get(); resp = c.prepareGetUsers("joe").get();
assertFalse("user should not exist after being deleted", resp.hasUsers()); assertFalse("user should not exist after being deleted", resp.hasUsers());
} }
@ -114,7 +114,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
new String[]{"body", "title"}, new BytesArray("{\"query\": {\"match_all\": {}}}")) new String[]{"body", "title"}, new BytesArray("{\"query\": {\"match_all\": {}}}"))
.get(); .get();
logger.error("--> waiting for .shield index"); logger.error("--> waiting for .shield index");
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ensureGreen(ShieldTemplateService.SECURITY_INDEX_NAME);
logger.info("--> retrieving role"); logger.info("--> retrieving role");
GetRolesResponse resp = c.prepareGetRoles().names("test_role").get(); GetRolesResponse resp = c.prepareGetRoles().names("test_role").get();
assertTrue("role should exist", resp.isExists()); assertTrue("role should exist", resp.isExists());
@ -167,9 +167,9 @@ public class ESNativeTests extends ShieldIntegTestCase {
c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get();
refresh(); refresh();
logger.error("--> waiting for .shield index"); logger.error("--> waiting for .shield index");
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ensureGreen(ShieldTemplateService.SECURITY_INDEX_NAME);
logger.info("--> retrieving user"); logger.info("--> retrieving user");
GetUsersResponse resp = c.prepareGetUsers().usernames("joe").get(); GetUsersResponse resp = c.prepareGetUsers("joe").get();
assertTrue("user should exist", resp.hasUsers()); assertTrue("user should exist", resp.hasUsers());
createIndex("idx"); createIndex("idx");
@ -189,9 +189,9 @@ public class ESNativeTests extends ShieldIntegTestCase {
c.preparePutUser("joe", "s3krit".toCharArray(), ShieldSettingsSource.DEFAULT_ROLE).get(); c.preparePutUser("joe", "s3krit".toCharArray(), ShieldSettingsSource.DEFAULT_ROLE).get();
refresh(); refresh();
logger.error("--> waiting for .shield index"); logger.error("--> waiting for .shield index");
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ensureGreen(ShieldTemplateService.SECURITY_INDEX_NAME);
logger.info("--> retrieving user"); logger.info("--> retrieving user");
GetUsersResponse resp = c.prepareGetUsers().usernames("joe").get(); GetUsersResponse resp = c.prepareGetUsers("joe").get();
assertTrue("user should exist", resp.hasUsers()); assertTrue("user should exist", resp.hasUsers());
assertThat(resp.users()[0].roles(), arrayContaining(ShieldSettingsSource.DEFAULT_ROLE)); assertThat(resp.users()[0].roles(), arrayContaining(ShieldSettingsSource.DEFAULT_ROLE));
@ -225,9 +225,9 @@ public class ESNativeTests extends ShieldIntegTestCase {
c.preparePutUser("joe", "s3krit".toCharArray(), ShieldSettingsSource.DEFAULT_ROLE).get(); c.preparePutUser("joe", "s3krit".toCharArray(), ShieldSettingsSource.DEFAULT_ROLE).get();
refresh(); refresh();
logger.error("--> waiting for .shield index"); logger.error("--> waiting for .shield index");
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ensureGreen(ShieldTemplateService.SECURITY_INDEX_NAME);
logger.info("--> retrieving user"); logger.info("--> retrieving user");
GetUsersResponse resp = c.prepareGetUsers().usernames("joe").get(); GetUsersResponse resp = c.prepareGetUsers("joe").get();
assertTrue("user should exist", resp.hasUsers()); assertTrue("user should exist", resp.hasUsers());
assertThat(resp.users()[0].roles(), arrayContaining(ShieldSettingsSource.DEFAULT_ROLE)); assertThat(resp.users()[0].roles(), arrayContaining(ShieldSettingsSource.DEFAULT_ROLE));
@ -264,7 +264,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get();
refresh(); refresh();
logger.error("--> waiting for .shield index"); logger.error("--> waiting for .shield index");
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ensureGreen(ShieldTemplateService.SECURITY_INDEX_NAME);
if (authenticate) { if (authenticate) {
final String token = basicAuthHeaderValue("joe", new SecuredString("s3krit".toCharArray())); final String token = basicAuthHeaderValue("joe", new SecuredString("s3krit".toCharArray()));
@ -311,7 +311,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get();
refresh(); refresh();
logger.error("--> waiting for .shield index"); logger.error("--> waiting for .shield index");
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME); ensureGreen(ShieldTemplateService.SECURITY_INDEX_NAME);
final String token = basicAuthHeaderValue("joe", new SecuredString("s3krit".toCharArray())); final String token = basicAuthHeaderValue("joe", new SecuredString("s3krit".toCharArray()));
ClusterHealthResponse response = client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster() ClusterHealthResponse response = client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster()

View File

@ -7,8 +7,8 @@
"paths": [ "/_shield/user/{username}" ], "paths": [ "/_shield/user/{username}" ],
"parts": { "parts": {
"username": { "username": {
"type" : "string", "type" : "list",
"description" : "The username of the User", "description" : "A comma-separated list of usernames",
"required" : false "required" : false
} }
}, },

View File

@ -12,7 +12,6 @@
username: "joe" username: "joe"
body: > body: >
{ {
"username" : "joe",
"password" : "s3krit", "password" : "s3krit",
"roles" : [ "admin" ], "roles" : [ "admin" ],
"full_name" : "Bazooka Joe", "full_name" : "Bazooka Joe",
@ -33,10 +32,9 @@
- do: - do:
shield.get_user: shield.get_user:
username: "joe" username: "joe"
- match: { found: true } - match: { joe.username: "joe" }
- match: { users.0.username: "joe" } - match: { joe.roles.0: "admin" }
- match: { users.0.roles.0: "admin" } - match: { joe.full_name: "Bazooka Joe" }
- match: { users.0.full_name: "Bazooka Joe" } - match: { joe.email: "joe@bazooka.gum" }
- match: { users.0.email: "joe@bazooka.gum" } - match: { joe.metadata.key1: "val1" }
- match: { users.0.metadata.key1: "val1" } - match: { joe.metadata.key2: "val2" }
- match: { users.0.metadata.key2: "val2" }

View File

@ -12,7 +12,6 @@
username: "joe" username: "joe"
body: > body: >
{ {
"username": "joe",
"password": "s3krit", "password": "s3krit",
"roles" : [ "admin" ] "roles" : [ "admin" ]
} }
@ -21,9 +20,8 @@
- do: - do:
shield.get_user: shield.get_user:
username: "joe" username: "joe"
- match: { found: true } - match: { joe.username: "joe" }
- match: { users.0.username: "joe" } - match: { joe.roles.0: "admin" }
- match: { users.0.roles.0: "admin" }
- do: - do:
headers: headers:
@ -36,7 +34,6 @@
username: "joe" username: "joe"
body: > body: >
{ {
"username" : "joe",
"password" : "s3krit2", "password" : "s3krit2",
"roles" : [ "admin", "foo" ], "roles" : [ "admin", "foo" ],
"full_name" : "Bazooka Joe", "full_name" : "Bazooka Joe",
@ -51,14 +48,13 @@
- do: - do:
shield.get_user: shield.get_user:
username: "joe" username: "joe"
- match: { found: true } - match: { joe.username: "joe" }
- match: { users.0.username: "joe" } - match: { joe.roles.0: "admin" }
- match: { users.0.roles.0: "admin" } - match: { joe.roles.1: "foo" }
- match: { users.0.roles.1: "foo" } - match: { joe.full_name: "Bazooka Joe" }
- match: { users.0.full_name: "Bazooka Joe" } - match: { joe.email: "joe@bazooka.gum" }
- match: { users.0.email: "joe@bazooka.gum" } - match: { joe.metadata.key1: "val1" }
- match: { users.0.metadata.key1: "val1" } - match: { joe.metadata.key2: "val2" }
- match: { users.0.metadata.key2: "val2" }
- do: - do:
headers: headers:

View File

@ -0,0 +1,12 @@
"Get missing user":
- do:
catch: missing
shield.get_user:
username: 'foo'
---
"Get missing (multiple) users":
- do:
catch: missing
shield.get_user:
username: [ 'foo', 'bar' ]