[Security] Add SAML authentication support (elastic/x-pack-elasticsearch#3646)
Introduces: - SAML Realm - REST & Transport actions to support SAML single signon / signout - Tests for above - More XML than you ever wanted to see. Original commit: elastic/x-pack-elasticsearch@b0fe7bb652
This commit is contained in:
parent
50864eabce
commit
47213f5675
|
@ -49,8 +49,8 @@ dependencies {
|
|||
//testCompile project(path: ':core:cli', configuration: 'runtime')
|
||||
testCompile 'org.elasticsearch:securemock:1.2'
|
||||
testCompile "org.elasticsearch:mocksocket:${versions.mocksocket}"
|
||||
testCompile 'org.slf4j:slf4j-log4j12:1.6.2'
|
||||
testCompile 'org.slf4j:slf4j-api:1.6.2'
|
||||
testCompile "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}"
|
||||
testCompile "org.slf4j:slf4j-api:${versions.slf4j}"
|
||||
testCompile project(path: ':modules:reindex', configuration: 'runtime')
|
||||
testCompile project(path: ':modules:parent-join', configuration: 'runtime')
|
||||
testCompile project(path: ':modules:analysis-common', configuration: 'runtime')
|
||||
|
|
|
@ -13,3 +13,10 @@ logger.xpack_security_audit_logfile.name = org.elasticsearch.xpack.security.audi
|
|||
logger.xpack_security_audit_logfile.level = info
|
||||
logger.xpack_security_audit_logfile.appenderRef.audit_rolling.ref = audit_rolling
|
||||
logger.xpack_security_audit_logfile.additivity = false
|
||||
|
||||
logger.xmlsig.name = org.apache.xml.security.signature.XMLSignature
|
||||
logger.xmlsig.level = error
|
||||
logger.samlxml_decrypt.name = org.opensaml.xmlsec.encryption.support.Decrypter
|
||||
logger.samlxml_decrypt.level = fatal
|
||||
logger.saml2_decrypt.name = org.opensaml.saml.saml2.encryption.Decrypter
|
||||
logger.saml2_decrypt.level = fatal
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import org.elasticsearch.action.Action;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* Action for authenticating using SAML assertions
|
||||
*/
|
||||
public final class SamlAuthenticateAction
|
||||
extends Action<SamlAuthenticateRequest, SamlAuthenticateResponse, SamlAuthenticateRequestBuilder> {
|
||||
|
||||
public static final String NAME = "cluster:admin/xpack/security/saml/authenticate";
|
||||
public static final SamlAuthenticateAction INSTANCE = new SamlAuthenticateAction();
|
||||
|
||||
private SamlAuthenticateAction() {
|
||||
super(NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlAuthenticateRequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
||||
return new SamlAuthenticateRequestBuilder(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlAuthenticateResponse newResponse() {
|
||||
return new SamlAuthenticateResponse();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
|
||||
/**
|
||||
* Represents a request to authenticate using SAML assertions.
|
||||
*/
|
||||
public final class SamlAuthenticateRequest extends ActionRequest {
|
||||
|
||||
private byte[] saml;
|
||||
private List<String> validRequestIds;
|
||||
|
||||
public SamlAuthenticateRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] getSaml() {
|
||||
return saml;
|
||||
}
|
||||
|
||||
public void setSaml(byte[] saml) {
|
||||
this.saml = saml;
|
||||
}
|
||||
|
||||
public List<String> getValidRequestIds() {
|
||||
return validRequestIds;
|
||||
}
|
||||
|
||||
public void setValidRequestIds(List<String> validRequestIds) {
|
||||
this.validRequestIds = validRequestIds;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* Request builder used to populate a {@link SamlAuthenticateRequest}
|
||||
*/
|
||||
public final class SamlAuthenticateRequestBuilder
|
||||
extends ActionRequestBuilder<SamlAuthenticateRequest, SamlAuthenticateResponse, SamlAuthenticateRequestBuilder> {
|
||||
|
||||
public SamlAuthenticateRequestBuilder(ElasticsearchClient client) {
|
||||
super(client, SamlAuthenticateAction.INSTANCE, new SamlAuthenticateRequest());
|
||||
}
|
||||
|
||||
public SamlAuthenticateRequestBuilder saml(byte[] saml) {
|
||||
request.setSaml(saml);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SamlAuthenticateRequestBuilder validRequestIds(List<String> validRequestIds) {
|
||||
request.setValidRequestIds(validRequestIds);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
|
||||
/**
|
||||
* The response from converting a SAML assertion into a security token.
|
||||
* Actually nothing SAML specific in this...
|
||||
*/
|
||||
public final class SamlAuthenticateResponse extends ActionResponse {
|
||||
|
||||
private String principal;
|
||||
private String tokenString;
|
||||
private String refreshToken;
|
||||
private TimeValue expiresIn;
|
||||
|
||||
public SamlAuthenticateResponse() {
|
||||
}
|
||||
|
||||
public SamlAuthenticateResponse(String principal, String tokenString, String refreshToken, TimeValue expiresIn) {
|
||||
this.principal = principal;
|
||||
this.tokenString = tokenString;
|
||||
this.refreshToken = refreshToken;
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
public String getPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
|
||||
public String getTokenString() {
|
||||
return tokenString;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public TimeValue getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeString(principal);
|
||||
out.writeString(tokenString);
|
||||
out.writeString(refreshToken);
|
||||
expiresIn.writeTo(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
principal = in.readString();
|
||||
tokenString = in.readString();
|
||||
refreshToken = in.readString();
|
||||
expiresIn = new TimeValue(in);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import org.elasticsearch.action.Action;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* Action to perform IdP-initiated logout for a SAML-SSO user
|
||||
*/
|
||||
public final class SamlInvalidateSessionAction
|
||||
extends Action<SamlInvalidateSessionRequest, SamlInvalidateSessionResponse, SamlInvalidateSessionRequestBuilder> {
|
||||
|
||||
public static final String NAME = "cluster:admin/xpack/security/saml/invalidate";
|
||||
public static final SamlInvalidateSessionAction INSTANCE = new SamlInvalidateSessionAction();
|
||||
|
||||
private SamlInvalidateSessionAction() {
|
||||
super(NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlInvalidateSessionRequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
||||
return new SamlInvalidateSessionRequestBuilder(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlInvalidateSessionResponse newResponse() {
|
||||
return new SamlInvalidateSessionResponse();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
|
||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||
|
||||
/**
|
||||
* Represents a request to invalidate a SAML session using a {@code LogoutRequest}.
|
||||
*/
|
||||
public final class SamlInvalidateSessionRequest extends ActionRequest {
|
||||
|
||||
@Nullable
|
||||
private String realmName;
|
||||
|
||||
@Nullable
|
||||
private String assertionConsumerServiceURL;
|
||||
|
||||
private String queryString;
|
||||
|
||||
public SamlInvalidateSessionRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
ActionRequestValidationException validationException = null;
|
||||
if (Strings.isNullOrEmpty(queryString)) {
|
||||
validationException = addValidationError("queryString is missing", validationException);
|
||||
}
|
||||
return validationException;
|
||||
}
|
||||
|
||||
public String getQueryString() {
|
||||
return queryString;
|
||||
}
|
||||
|
||||
public void setQueryString(String queryString) {
|
||||
this.queryString = queryString;
|
||||
}
|
||||
|
||||
public String getRealmName() {
|
||||
return realmName;
|
||||
}
|
||||
|
||||
public void setRealmName(String realmName) {
|
||||
this.realmName = realmName;
|
||||
}
|
||||
|
||||
public String getAssertionConsumerServiceURL() {
|
||||
return assertionConsumerServiceURL;
|
||||
}
|
||||
|
||||
public void setAssertionConsumerServiceURL(String assertionConsumerServiceURL) {
|
||||
this.assertionConsumerServiceURL = assertionConsumerServiceURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "{" +
|
||||
"realmName='" + realmName + '\'' +
|
||||
", assertionConsumerServiceURL='" + assertionConsumerServiceURL + '\'' +
|
||||
", url-query=" + queryString.length() + " chars" +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* Request builder used to populate a {@link SamlInvalidateSessionRequest}
|
||||
*/
|
||||
public final class SamlInvalidateSessionRequestBuilder
|
||||
extends ActionRequestBuilder<SamlInvalidateSessionRequest, SamlInvalidateSessionResponse, SamlInvalidateSessionRequestBuilder> {
|
||||
|
||||
public SamlInvalidateSessionRequestBuilder(ElasticsearchClient client) {
|
||||
super(client, SamlInvalidateSessionAction.INSTANCE, new SamlInvalidateSessionRequest());
|
||||
}
|
||||
|
||||
public SamlInvalidateSessionRequestBuilder queryString(String query) {
|
||||
request.setQueryString(query);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SamlInvalidateSessionRequestBuilder realmName(String name) {
|
||||
request.setRealmName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SamlInvalidateSessionRequestBuilder assertionConsumerService(String url) {
|
||||
request.setAssertionConsumerServiceURL(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
/**
|
||||
* Response to an IdP-initiated SAML {@code <LogoutRequest>}
|
||||
*/
|
||||
public final class SamlInvalidateSessionResponse extends ActionResponse {
|
||||
|
||||
private String realmName;
|
||||
private int count;
|
||||
private String redirectUrl;
|
||||
|
||||
public SamlInvalidateSessionResponse() {
|
||||
}
|
||||
|
||||
public SamlInvalidateSessionResponse(String realmName, int count, String redirectUrl) {
|
||||
this.realmName = realmName;
|
||||
this.count = count;
|
||||
this.redirectUrl = redirectUrl;
|
||||
}
|
||||
|
||||
public String getRealmName() {
|
||||
return realmName;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public String getRedirectUrl() {
|
||||
return redirectUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeString(realmName);
|
||||
out.writeInt(count);
|
||||
out.writeString(redirectUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
realmName = in.readString();
|
||||
count = in.readInt();
|
||||
redirectUrl = in.readString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import org.elasticsearch.action.Action;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* Action for initiating a logout process for a SAML-SSO user
|
||||
*/
|
||||
public final class SamlLogoutAction extends Action<SamlLogoutRequest, SamlLogoutResponse, SamlLogoutRequestBuilder> {
|
||||
|
||||
public static final String NAME = "cluster:admin/xpack/security/saml/logout";
|
||||
public static final SamlLogoutAction INSTANCE = new SamlLogoutAction();
|
||||
|
||||
private SamlLogoutAction() {
|
||||
super(NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlLogoutRequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
||||
return new SamlLogoutRequestBuilder(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlLogoutResponse newResponse() {
|
||||
return new SamlLogoutResponse();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
|
||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||
|
||||
/**
|
||||
* Represents a request to prepare a SAML {@code <LogoutRequest>}.
|
||||
*/
|
||||
public final class SamlLogoutRequest extends ActionRequest {
|
||||
|
||||
private String token;
|
||||
@Nullable
|
||||
private String refreshToken;
|
||||
|
||||
public SamlLogoutRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
ActionRequestValidationException validationException = null;
|
||||
if (Strings.isNullOrEmpty(token)) {
|
||||
validationException = addValidationError("token is missing", validationException);
|
||||
}
|
||||
return validationException;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public void setRefreshToken(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* Request builder used to populate a {@link SamlLogoutRequest}
|
||||
*/
|
||||
public final class SamlLogoutRequestBuilder extends ActionRequestBuilder<SamlLogoutRequest,
|
||||
SamlLogoutResponse, SamlLogoutRequestBuilder> {
|
||||
|
||||
public SamlLogoutRequestBuilder(ElasticsearchClient client) {
|
||||
super(client, SamlLogoutAction.INSTANCE, new SamlLogoutRequest());
|
||||
}
|
||||
|
||||
public SamlLogoutRequestBuilder token(String token) {
|
||||
request.setToken(token);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
/**
|
||||
* Response containing a SAML {@code <LogoutRequest>} for the current user
|
||||
*/
|
||||
public final class SamlLogoutResponse extends ActionResponse {
|
||||
|
||||
private String redirectUrl;
|
||||
|
||||
public SamlLogoutResponse() {
|
||||
}
|
||||
|
||||
public SamlLogoutResponse(String redirectUrl) {
|
||||
this.redirectUrl = redirectUrl;
|
||||
}
|
||||
|
||||
public String getRedirectUrl() {
|
||||
return redirectUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeString(redirectUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
redirectUrl = in.readString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import org.elasticsearch.action.Action;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* Action for initiating an authentication process using SAML assertions
|
||||
*/
|
||||
public final class SamlPrepareAuthenticationAction
|
||||
extends Action<SamlPrepareAuthenticationRequest, SamlPrepareAuthenticationResponse, SamlPrepareAuthenticationRequestBuilder> {
|
||||
|
||||
public static final String NAME = "cluster:admin/xpack/security/saml/prepare";
|
||||
public static final SamlPrepareAuthenticationAction INSTANCE = new SamlPrepareAuthenticationAction();
|
||||
|
||||
private SamlPrepareAuthenticationAction() {
|
||||
super(NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlPrepareAuthenticationRequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
||||
return new SamlPrepareAuthenticationRequestBuilder(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlPrepareAuthenticationResponse newResponse() {
|
||||
return new SamlPrepareAuthenticationResponse();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
/**
|
||||
* Represents a request to prepare a SAML {@code <AuthnRequest>}.
|
||||
*/
|
||||
public final class SamlPrepareAuthenticationRequest extends ActionRequest {
|
||||
|
||||
@Nullable
|
||||
private String realmName;
|
||||
|
||||
@Nullable
|
||||
private String assertionConsumerServiceURL;
|
||||
|
||||
public SamlPrepareAuthenticationRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getRealmName() {
|
||||
return realmName;
|
||||
}
|
||||
|
||||
public void setRealmName(String realmName) {
|
||||
this.realmName = realmName;
|
||||
}
|
||||
|
||||
public String getAssertionConsumerServiceURL() {
|
||||
return assertionConsumerServiceURL;
|
||||
}
|
||||
|
||||
public void setAssertionConsumerServiceURL(String assertionConsumerServiceURL) {
|
||||
this.assertionConsumerServiceURL = assertionConsumerServiceURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "{" +
|
||||
"realmName=" + realmName +
|
||||
", assertionConsumerServiceURL=" + assertionConsumerServiceURL +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
realmName = in.readOptionalString();
|
||||
assertionConsumerServiceURL = in.readOptionalString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeOptionalString(realmName);
|
||||
out.writeOptionalString(assertionConsumerServiceURL);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* Request builder used to populate a {@link SamlPrepareAuthenticationRequest}
|
||||
*/
|
||||
public final class SamlPrepareAuthenticationRequestBuilder extends ActionRequestBuilder<SamlPrepareAuthenticationRequest,
|
||||
SamlPrepareAuthenticationResponse, SamlPrepareAuthenticationRequestBuilder> {
|
||||
|
||||
public SamlPrepareAuthenticationRequestBuilder(ElasticsearchClient client) {
|
||||
super(client, SamlPrepareAuthenticationAction.INSTANCE, new SamlPrepareAuthenticationRequest());
|
||||
}
|
||||
|
||||
public SamlPrepareAuthenticationRequestBuilder realmName(String name) {
|
||||
request.setRealmName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SamlPrepareAuthenticationRequestBuilder assertionConsumerService(String url) {
|
||||
request.setAssertionConsumerServiceURL(url);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
/**
|
||||
* Response containing a SAML {@code <AuthnRequest>} for a specific realm.
|
||||
*/
|
||||
public final class SamlPrepareAuthenticationResponse extends ActionResponse {
|
||||
|
||||
private String realmName;
|
||||
private String requestId;
|
||||
private String redirectUrl;
|
||||
|
||||
public SamlPrepareAuthenticationResponse() {
|
||||
}
|
||||
|
||||
public SamlPrepareAuthenticationResponse(String realmName, String requestId, String redirectUrl) {
|
||||
this.realmName = realmName;
|
||||
this.requestId = requestId;
|
||||
this.redirectUrl = redirectUrl;
|
||||
}
|
||||
|
||||
public String getRealmName() {
|
||||
return realmName;
|
||||
}
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public String getRedirectUrl() {
|
||||
return redirectUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeString(redirectUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
redirectUrl = in.readString();
|
||||
}
|
||||
|
||||
}
|
|
@ -5,8 +5,6 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.action.user;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.elasticsearch.action.Action;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.elasticsearch.xpack.security.authc.esnative.NativeRealmSettings;
|
|||
import org.elasticsearch.xpack.security.authc.file.FileRealmSettings;
|
||||
import org.elasticsearch.xpack.security.authc.ldap.LdapRealmSettings;
|
||||
import org.elasticsearch.xpack.security.authc.pki.PkiRealmSettings;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlRealmSettings;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -31,6 +32,7 @@ public final class InternalRealmsSettings {
|
|||
map.put(LdapRealmSettings.AD_TYPE, LdapRealmSettings.getSettings(LdapRealmSettings.AD_TYPE));
|
||||
map.put(LdapRealmSettings.LDAP_TYPE, LdapRealmSettings.getSettings(LdapRealmSettings.LDAP_TYPE));
|
||||
map.put(PkiRealmSettings.TYPE, PkiRealmSettings.getSettings());
|
||||
map.put(SamlRealmSettings.TYPE, SamlRealmSettings.getSettings());
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,13 +154,17 @@ public class RealmSettings {
|
|||
}
|
||||
|
||||
private static void validateRealm(String name, Settings settings, Map<String, Set<Setting<?>>> validSettings) {
|
||||
final String type = TYPE_SETTING.get(settings);
|
||||
final String type = getRealmType(settings);
|
||||
if (isNullOrEmpty(type)) {
|
||||
throw new IllegalArgumentException("missing realm type [" + getFullSettingKey(name, TYPE_SETTING) + "] for realm");
|
||||
}
|
||||
validateRealm(name, type, settings, validSettings.get(type));
|
||||
}
|
||||
|
||||
public static String getRealmType(Settings settings) {
|
||||
return TYPE_SETTING.get(settings);
|
||||
}
|
||||
|
||||
private static void validateRealm(String name, String type, Settings settings, Set<Setting<?>> validSettings) {
|
||||
if (validSettings == null) {
|
||||
// For backwards compatibility, we assume that is we don't know the valid settings for a realm.type then everything
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.util.set.Sets;
|
||||
import org.elasticsearch.xpack.ssl.SSLConfigurationSettings;
|
||||
import org.elasticsearch.xpack.ssl.X509KeyPairSettings;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SamlRealmSettings {
|
||||
|
||||
public static final String TYPE = "saml";
|
||||
private static final String TRANSIENT_NAMEID_FORMAT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
|
||||
|
||||
// these settings will be used under the prefix xpack.security.authc.realms.REALM_NAME.
|
||||
private static final String IDP_METADATA_SETTING_PREFIX = "idp.metadata.";
|
||||
|
||||
public static final Setting<String> IDP_ENTITY_ID = Setting.simpleString("idp.entity_id", Setting.Property.NodeScope);
|
||||
public static final Setting<String> IDP_METADATA_PATH
|
||||
= Setting.simpleString(IDP_METADATA_SETTING_PREFIX + "path", Setting.Property.NodeScope);
|
||||
public static final Setting<TimeValue> IDP_METADATA_HTTP_REFRESH
|
||||
= Setting.timeSetting(IDP_METADATA_SETTING_PREFIX + "http.refresh", TimeValue.timeValueHours(1), Setting.Property.NodeScope);
|
||||
public static final Setting<Boolean> IDP_SINGLE_LOGOUT = Setting.boolSetting("idp.use_single_logout", true, Setting.Property.NodeScope);
|
||||
|
||||
public static final Setting<String> SP_ENTITY_ID = Setting.simpleString("sp.entity_id", Setting.Property.NodeScope);
|
||||
public static final Setting<String> SP_ACS = Setting.simpleString("sp.acs", Setting.Property.NodeScope);
|
||||
public static final Setting<String> SP_LOGOUT = Setting.simpleString("sp.logout", Setting.Property.NodeScope);
|
||||
|
||||
public static final Setting<String> NAMEID_FORMAT = new Setting<>("nameid_format", s -> TRANSIENT_NAMEID_FORMAT, Function.identity(),
|
||||
Setting.Property.NodeScope);
|
||||
public static final Setting<Boolean> FORCE_AUTHN = Setting.boolSetting("force_authn", false, Setting.Property.NodeScope);
|
||||
public static final Setting<Boolean> POPULATE_USER_METADATA = Setting.boolSetting("populate_user_metadata", true,
|
||||
Setting.Property.NodeScope);
|
||||
|
||||
public static final AttributeSetting PRINCIPAL_ATTRIBUTE = new AttributeSetting("principal");
|
||||
public static final AttributeSetting GROUPS_ATTRIBUTE = new AttributeSetting("groups");
|
||||
public static final AttributeSetting DN_ATTRIBUTE = new AttributeSetting("dn");
|
||||
public static final AttributeSetting NAME_ATTRIBUTE = new AttributeSetting("name");
|
||||
public static final AttributeSetting MAIL_ATTRIBUTE = new AttributeSetting("mail");
|
||||
|
||||
public static final X509KeyPairSettings ENCRYPTION_SETTINGS = new X509KeyPairSettings("encryption.", false);
|
||||
public static final Setting<String> ENCRYPTION_KEY_ALIAS = new Setting<>("encryption.keystore.alias", "key", Function.identity(),
|
||||
Setting.Property.NodeScope);
|
||||
|
||||
public static final X509KeyPairSettings SIGNING_SETTINGS = new X509KeyPairSettings("signing.", false);
|
||||
public static final Setting<String> SIGNING_KEY_ALIAS = new Setting<>("signing.keystore.alias", "key", Function.identity(),
|
||||
Setting.Property.NodeScope);
|
||||
public static final Setting<List<String>> SIGNING_MESSAGE_TYPES = Setting.listSetting("signing.saml_messages",
|
||||
Collections.singletonList("*"), Function.identity(), Setting.Property.NodeScope);
|
||||
|
||||
public static final Setting<TimeValue> CLOCK_SKEW = Setting.positiveTimeSetting("allowed_clock_skew", TimeValue.timeValueMinutes(3),
|
||||
Setting.Property.NodeScope);
|
||||
|
||||
public static final String SSL_PREFIX = "ssl.";
|
||||
|
||||
private SamlRealmSettings() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link Setting setting configuration} for this realm type
|
||||
*/
|
||||
public static Set<Setting<?>> getSettings() {
|
||||
final Set<Setting<?>> set = Sets.newHashSet(IDP_ENTITY_ID, IDP_METADATA_PATH,
|
||||
SP_ENTITY_ID, SP_ACS, SP_LOGOUT,
|
||||
NAMEID_FORMAT, FORCE_AUTHN,
|
||||
CLOCK_SKEW,
|
||||
ENCRYPTION_KEY_ALIAS, SIGNING_KEY_ALIAS, SIGNING_MESSAGE_TYPES);
|
||||
set.addAll(ENCRYPTION_SETTINGS.getAllSettings());
|
||||
set.addAll(SIGNING_SETTINGS.getAllSettings());
|
||||
set.addAll(SSLConfigurationSettings.withPrefix(SSL_PREFIX).getAllSettings());
|
||||
set.addAll(PRINCIPAL_ATTRIBUTE.settings());
|
||||
set.addAll(GROUPS_ATTRIBUTE.settings());
|
||||
set.addAll(DN_ATTRIBUTE.settings());
|
||||
set.addAll(NAME_ATTRIBUTE.settings());
|
||||
set.addAll(MAIL_ATTRIBUTE.settings());
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SAML realm offers a number of settings that rely on attributes that are populate by the Identity Provider in the SAML Response.
|
||||
* Each attribute has 2 settings:
|
||||
* <ul>
|
||||
* <li>The name of the SAML attribute to use</li>
|
||||
* <li>A java pattern (regex) to apply to that attribute value in order to extract the substring that should be used.</li>
|
||||
* </ul>
|
||||
* For example, the Elasticsearch User Principal could be configured to come from the SAML "mail" attribute, and extract only the
|
||||
* local-port of the user's email address (i.e. the name before the '@').
|
||||
* This class encapsulates those 2 settings.
|
||||
*/
|
||||
public static final class AttributeSetting {
|
||||
static final String ATTRIBUTES_PREFIX = "attributes.";
|
||||
static final String ATTRIBUTE_PATTERNS_PREFIX = "attribute_patterns.";
|
||||
|
||||
private final Setting<String> attribute;
|
||||
private final Setting<String> pattern;
|
||||
|
||||
AttributeSetting(String name) {
|
||||
attribute = Setting.simpleString(ATTRIBUTES_PREFIX + name, Setting.Property.NodeScope);
|
||||
pattern = Setting.simpleString(ATTRIBUTE_PATTERNS_PREFIX + name, Setting.Property.NodeScope);
|
||||
}
|
||||
|
||||
public Collection<Setting<?>> settings() {
|
||||
return Arrays.asList(getAttribute(), getPattern());
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return getAttribute().getKey();
|
||||
}
|
||||
|
||||
public Setting<String> getAttribute() {
|
||||
return attribute;
|
||||
}
|
||||
|
||||
public Setting<String> getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,11 +5,6 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authz.privilege;
|
||||
|
||||
import org.apache.lucene.util.automaton.Automaton;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.elasticsearch.xpack.security.support.Automatons;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
|
@ -18,6 +13,13 @@ import java.util.Set;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.lucene.util.automaton.Automaton;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.elasticsearch.xpack.security.action.token.InvalidateTokenAction;
|
||||
import org.elasticsearch.xpack.security.action.token.RefreshTokenAction;
|
||||
import org.elasticsearch.xpack.security.support.Automatons;
|
||||
|
||||
import static org.elasticsearch.xpack.security.support.Automatons.minusAndMinimize;
|
||||
import static org.elasticsearch.xpack.security.support.Automatons.patterns;
|
||||
|
||||
|
@ -25,6 +27,8 @@ public final class ClusterPrivilege extends Privilege {
|
|||
|
||||
// shared automatons
|
||||
private static final Automaton MANAGE_SECURITY_AUTOMATON = patterns("cluster:admin/xpack/security/*");
|
||||
private static final Automaton MANAGE_SAML_AUTOMATON = patterns("cluster:admin/xpack/security/saml/*",
|
||||
InvalidateTokenAction.NAME, RefreshTokenAction.NAME);
|
||||
private static final Automaton MONITOR_AUTOMATON = patterns("cluster:monitor/*");
|
||||
private static final Automaton MONITOR_ML_AUTOMATON = patterns("cluster:monitor/xpack/ml/*");
|
||||
private static final Automaton MONITOR_WATCHER_AUTOMATON = patterns("cluster:monitor/xpack/watcher/*");
|
||||
|
@ -50,6 +54,7 @@ public final class ClusterPrivilege extends Privilege {
|
|||
new ClusterPrivilege("manage_ingest_pipelines", MANAGE_INGEST_PIPELINE_AUTOMATON);
|
||||
public static final ClusterPrivilege TRANSPORT_CLIENT = new ClusterPrivilege("transport_client", TRANSPORT_CLIENT_AUTOMATON);
|
||||
public static final ClusterPrivilege MANAGE_SECURITY = new ClusterPrivilege("manage_security", MANAGE_SECURITY_AUTOMATON);
|
||||
public static final ClusterPrivilege MANAGE_SAML = new ClusterPrivilege("manage_saml", MANAGE_SAML_AUTOMATON);
|
||||
public static final ClusterPrivilege MANAGE_PIPELINE = new ClusterPrivilege("manage_pipeline", "cluster:admin/ingest/pipeline/*");
|
||||
|
||||
public static final Predicate<String> ACTION_MATCHER = ClusterPrivilege.ALL.predicate();
|
||||
|
@ -67,6 +72,7 @@ public final class ClusterPrivilege extends Privilege {
|
|||
.put("manage_ingest_pipelines", MANAGE_INGEST_PIPELINES)
|
||||
.put("transport_client", TRANSPORT_CLIENT)
|
||||
.put("manage_security", MANAGE_SECURITY)
|
||||
.put("manage_saml", MANAGE_SAML)
|
||||
.put("manage_pipeline", MANAGE_PIPELINE)
|
||||
.immutableMap();
|
||||
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authz.store;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction;
|
||||
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
||||
|
@ -74,7 +79,7 @@ public class ReservedRolesStore {
|
|||
null,
|
||||
MetadataUtils.DEFAULT_RESERVED_METADATA))
|
||||
.put(KibanaUser.ROLE_NAME, new RoleDescriptor(KibanaUser.ROLE_NAME,
|
||||
new String[] { "monitor", "manage_index_templates", MonitoringBulkAction.NAME },
|
||||
new String[] { "monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml" },
|
||||
new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*", ".reporting-*").privileges("all").build(),
|
||||
RoleDescriptor.IndicesPrivileges.builder()
|
||||
|
@ -130,4 +135,4 @@ public class ReservedRolesStore {
|
|||
public static Set<String> names() {
|
||||
return RESERVED_ROLES.keySet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,11 @@ import org.elasticsearch.xpack.security.action.rolemapping.GetRoleMappingsReques
|
|||
import org.elasticsearch.xpack.security.action.rolemapping.GetRoleMappingsResponse;
|
||||
import org.elasticsearch.xpack.security.action.rolemapping.PutRoleMappingAction;
|
||||
import org.elasticsearch.xpack.security.action.rolemapping.PutRoleMappingRequestBuilder;
|
||||
import org.elasticsearch.xpack.security.action.saml.SamlAuthenticateRequest;
|
||||
import org.elasticsearch.xpack.security.action.saml.SamlAuthenticateRequestBuilder;
|
||||
import org.elasticsearch.xpack.security.action.saml.SamlAuthenticateResponse;
|
||||
import org.elasticsearch.xpack.security.action.saml.SamlAuthenticateAction;
|
||||
import org.elasticsearch.xpack.security.action.saml.SamlPrepareAuthenticationRequestBuilder;
|
||||
import org.elasticsearch.xpack.security.action.token.CreateTokenAction;
|
||||
import org.elasticsearch.xpack.security.action.token.CreateTokenRequest;
|
||||
import org.elasticsearch.xpack.security.action.token.CreateTokenRequestBuilder;
|
||||
|
@ -73,6 +78,7 @@ import org.elasticsearch.xpack.security.action.user.SetEnabledRequestBuilder;
|
|||
import org.elasticsearch.xpack.security.action.user.SetEnabledResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A wrapper to elasticsearch clients that exposes all Security related APIs
|
||||
|
@ -286,6 +292,21 @@ public class SecurityClient {
|
|||
client.execute(InvalidateTokenAction.INSTANCE, request, listener);
|
||||
}
|
||||
|
||||
public SamlAuthenticateRequestBuilder prepareSamlAuthenticate(byte[] xmlContent, List<String> validIds) {
|
||||
final SamlAuthenticateRequestBuilder builder = new SamlAuthenticateRequestBuilder(client);
|
||||
builder.saml(xmlContent);
|
||||
builder.validRequestIds(validIds);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public void samlAuthenticate(SamlAuthenticateRequest request, ActionListener< SamlAuthenticateResponse> listener) {
|
||||
client.execute(SamlAuthenticateAction.INSTANCE, request, listener);
|
||||
}
|
||||
|
||||
public SamlPrepareAuthenticationRequestBuilder prepareSamlPrepareAuthentication() {
|
||||
return new SamlPrepareAuthenticationRequestBuilder(client);
|
||||
}
|
||||
|
||||
public CreateTokenRequestBuilder prepareRefreshToken(String refreshToken) {
|
||||
return new CreateTokenRequestBuilder(client, RefreshTokenAction.INSTANCE)
|
||||
.setRefreshToken(refreshToken)
|
||||
|
|
|
@ -16,12 +16,15 @@ public class MetadataUtils {
|
|||
public static final String RESERVED_METADATA_KEY = RESERVED_PREFIX + "reserved";
|
||||
public static final Map<String, Object> DEFAULT_RESERVED_METADATA = Collections.singletonMap(RESERVED_METADATA_KEY, true);
|
||||
|
||||
private MetadataUtils() {}
|
||||
private MetadataUtils() {
|
||||
}
|
||||
|
||||
public static void writeValue(StringBuilder sb, Object object) {
|
||||
if (object instanceof Map) {
|
||||
if (object == null) {
|
||||
sb.append(object);
|
||||
} else if (object instanceof Map) {
|
||||
sb.append("{");
|
||||
for (Map.Entry<String, Object> entry : ((Map<String, Object>)object).entrySet()) {
|
||||
for (Map.Entry<String, Object> entry : ((Map<String, Object>) object).entrySet()) {
|
||||
sb.append(entry.getKey()).append("=");
|
||||
writeValue(sb, entry.getValue());
|
||||
}
|
||||
|
|
|
@ -86,10 +86,14 @@ import org.elasticsearch.common.SuppressForbidden;
|
|||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.network.InetAddressHelper;
|
||||
import org.elasticsearch.common.network.NetworkAddress;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
import static org.elasticsearch.xpack.ssl.SSLConfigurationSettings.getKeyStoreType;
|
||||
|
||||
/**
|
||||
* Utility methods that deal with {@link Certificate}, {@link KeyStore}, {@link X509ExtendedTrustManager}, {@link X509ExtendedKeyManager}
|
||||
* and other certificate related objects.
|
||||
|
@ -164,6 +168,52 @@ public class CertUtils {
|
|||
throw new IllegalStateException("failed to find a X509ExtendedKeyManager");
|
||||
}
|
||||
|
||||
public static X509ExtendedKeyManager getKeyManager(X509KeyPairSettings keyPair, Settings settings,
|
||||
@Nullable String trustStoreAlgorithm, Environment environment) {
|
||||
if (trustStoreAlgorithm == null) {
|
||||
trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||
}
|
||||
final KeyConfig keyConfig = createKeyConfig(keyPair, settings, trustStoreAlgorithm);
|
||||
if (keyConfig == null) {
|
||||
return null;
|
||||
} else {
|
||||
return keyConfig.createKeyManager(environment);
|
||||
}
|
||||
}
|
||||
|
||||
static KeyConfig createKeyConfig(X509KeyPairSettings keyPair, Settings settings, String trustStoreAlgorithm) {
|
||||
String keyPath = keyPair.keyPath.get(settings).orElse(null);
|
||||
String keyStorePath = keyPair.keystorePath.get(settings).orElse(null);
|
||||
|
||||
if (keyPath != null && keyStorePath != null) {
|
||||
throw new IllegalArgumentException("you cannot specify a keystore and key file");
|
||||
}
|
||||
|
||||
if (keyPath != null) {
|
||||
SecureString keyPassword = keyPair.keyPassword.get(settings);
|
||||
String certPath = keyPair.certificatePath.get(settings).orElse(null);
|
||||
if (certPath == null) {
|
||||
throw new IllegalArgumentException("you must specify the certificates [" + keyPair.certificatePath.getKey()
|
||||
+ "] to use with the key [" + keyPair.keyPath.getKey() + "]");
|
||||
}
|
||||
return new PEMKeyConfig(keyPath, keyPassword, certPath);
|
||||
}
|
||||
|
||||
if (keyStorePath != null) {
|
||||
SecureString keyStorePassword = keyPair.keystorePassword.get(settings);
|
||||
String keyStoreAlgorithm = keyPair.keystoreAlgorithm.get(settings);
|
||||
String keyStoreType = getKeyStoreType(keyPair.keystoreType, settings, keyStorePath);
|
||||
SecureString keyStoreKeyPassword = keyPair.keystoreKeyPassword.get(settings);
|
||||
if (keyStoreKeyPassword.length() == 0) {
|
||||
keyStoreKeyPassword = keyStorePassword;
|
||||
}
|
||||
return new StoreKeyConfig(keyStorePath, keyStoreType, keyStorePassword, keyStoreKeyPassword, keyStoreAlgorithm,
|
||||
trustStoreAlgorithm);
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link X509ExtendedTrustManager} based on the provided certificates
|
||||
*
|
||||
|
@ -244,7 +294,7 @@ public class CertUtils {
|
|||
return readCertificates(resolvedPaths);
|
||||
}
|
||||
|
||||
static Certificate[] readCertificates(List<Path> certPaths) throws CertificateException, IOException {
|
||||
public static Certificate[] readCertificates(List<Path> certPaths) throws CertificateException, IOException {
|
||||
List<Certificate> certificates = new ArrayList<>(certPaths.size());
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
for (Path path : certPaths) {
|
||||
|
|
|
@ -7,13 +7,6 @@ package org.elasticsearch.xpack.ssl;
|
|||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
|
@ -23,6 +16,16 @@ import org.elasticsearch.env.Environment;
|
|||
import org.elasticsearch.xpack.XPackSettings;
|
||||
import org.elasticsearch.xpack.ssl.cert.CertificateInfo;
|
||||
|
||||
import static org.elasticsearch.xpack.ssl.SSLConfigurationSettings.getKeyStoreType;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* Represents the configuration for an SSLContext
|
||||
*/
|
||||
|
@ -47,7 +50,7 @@ public final class SSLConfiguration {
|
|||
* @param settings the SSL specific settings; only the settings under a *.ssl. prefix
|
||||
*/
|
||||
SSLConfiguration(Settings settings) {
|
||||
this.keyConfig = createKeyConfig(settings, null);
|
||||
this.keyConfig = createKeyConfig(settings, (SSLConfiguration) null);
|
||||
this.trustConfig = createTrustConfig(settings, keyConfig, null);
|
||||
this.ciphers = getListOrDefault(SETTINGS_PARSER.ciphers, settings, XPackSettings.DEFAULT_CIPHERS);
|
||||
this.supportedProtocols = getListOrDefault(SETTINGS_PARSER.supportedProtocols, settings, XPackSettings.DEFAULT_SUPPORTED_PROTOCOLS);
|
||||
|
@ -180,43 +183,23 @@ public final class SSLConfiguration {
|
|||
}
|
||||
|
||||
private static KeyConfig createKeyConfig(Settings settings, SSLConfiguration global) {
|
||||
String keyStorePath = SETTINGS_PARSER.keystorePath.get(settings).orElse(null);
|
||||
String keyPath = SETTINGS_PARSER.keyPath.get(settings).orElse(null);
|
||||
if (keyPath != null && keyStorePath != null) {
|
||||
throw new IllegalArgumentException("you cannot specify a keystore and key file");
|
||||
} else if (keyStorePath == null && keyPath == null) {
|
||||
if (global != null) {
|
||||
return global.keyConfig();
|
||||
} else if (System.getProperty("javax.net.ssl.keyStore") != null) {
|
||||
// TODO: we should not support loading a keystore from sysprops...
|
||||
try (SecureString keystorePassword = new SecureString(System.getProperty("javax.net.ssl.keyStorePassword", ""))) {
|
||||
return new StoreKeyConfig(System.getProperty("javax.net.ssl.keyStore"), "jks", keystorePassword, keystorePassword,
|
||||
System.getProperty("ssl.KeyManagerFactory.algorithm", KeyManagerFactory.getDefaultAlgorithm()),
|
||||
System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
|
||||
}
|
||||
}
|
||||
return KeyConfig.NONE;
|
||||
final String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings);
|
||||
final KeyConfig config = CertUtils.createKeyConfig(SETTINGS_PARSER.x509KeyPair, settings, trustStoreAlgorithm);
|
||||
if (config != null) {
|
||||
return config;
|
||||
}
|
||||
|
||||
if (keyPath != null) {
|
||||
SecureString keyPassword = SETTINGS_PARSER.keyPassword.get(settings);
|
||||
String certPath = SETTINGS_PARSER.cert.get(settings).orElse(null);
|
||||
if (certPath == null) {
|
||||
throw new IllegalArgumentException("you must specify the certificates to use with the key");
|
||||
}
|
||||
return new PEMKeyConfig(keyPath, keyPassword, certPath);
|
||||
} else {
|
||||
SecureString keyStorePassword = SETTINGS_PARSER.keystorePassword.get(settings);
|
||||
String keyStoreAlgorithm = SETTINGS_PARSER.keystoreAlgorithm.get(settings);
|
||||
String keyStoreType = SSLConfigurationSettings.getKeyStoreType(SETTINGS_PARSER.keystoreType, settings, keyStorePath);
|
||||
SecureString keyStoreKeyPassword = SETTINGS_PARSER.keystoreKeyPassword.get(settings);
|
||||
if (keyStoreKeyPassword.length() == 0) {
|
||||
keyStoreKeyPassword = keyStorePassword;
|
||||
}
|
||||
String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings);
|
||||
return new StoreKeyConfig(keyStorePath, keyStoreType, keyStorePassword, keyStoreKeyPassword, keyStoreAlgorithm,
|
||||
trustStoreAlgorithm);
|
||||
if (global != null) {
|
||||
return global.keyConfig();
|
||||
}
|
||||
if (System.getProperty("javax.net.ssl.keyStore") != null) {
|
||||
// TODO: we should not support loading a keystore from sysprops...
|
||||
try (SecureString keystorePassword = new SecureString(System.getProperty("javax.net.ssl.keyStorePassword", ""))) {
|
||||
return new StoreKeyConfig(System.getProperty("javax.net.ssl.keyStore"), "jks", keystorePassword, keystorePassword,
|
||||
System.getProperty("ssl.KeyManagerFactory.algorithm", KeyManagerFactory.getDefaultAlgorithm()),
|
||||
System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
|
||||
}
|
||||
}
|
||||
return KeyConfig.NONE;
|
||||
}
|
||||
|
||||
private static TrustConfig createTrustConfig(Settings settings, KeyConfig keyConfig, SSLConfiguration global) {
|
||||
|
@ -247,7 +230,7 @@ public final class SSLConfiguration {
|
|||
} else if (trustStorePath != null) {
|
||||
SecureString trustStorePassword = SETTINGS_PARSER.truststorePassword.get(settings);
|
||||
String trustStoreAlgorithm = SETTINGS_PARSER.truststoreAlgorithm.get(settings);
|
||||
String trustStoreType = SSLConfigurationSettings.getKeyStoreType(SETTINGS_PARSER.truststoreType, settings, trustStorePath);
|
||||
String trustStoreType = getKeyStoreType(SETTINGS_PARSER.truststoreType, settings, trustStorePath);
|
||||
return new StoreTrustConfig(trustStorePath, trustStoreType, trustStorePassword, trustStoreAlgorithm);
|
||||
} else if (global == null && System.getProperty("javax.net.ssl.trustStore") != null) {
|
||||
try (SecureString truststorePassword = new SecureString(System.getProperty("javax.net.ssl.trustStorePassword", ""))) {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.ssl;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.security.KeyStore;
|
||||
import java.util.Arrays;
|
||||
|
@ -21,27 +20,23 @@ import org.elasticsearch.common.settings.SecureString;
|
|||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Setting.Property;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Bridges SSLConfiguration into the {@link Settings} framework, using {@link Setting} objects.
|
||||
*/
|
||||
public class SSLConfigurationSettings {
|
||||
|
||||
final X509KeyPairSettings x509KeyPair;
|
||||
|
||||
public final Setting<List<String>> ciphers;
|
||||
public final Setting<List<String>> supportedProtocols;
|
||||
public final Setting<Optional<String>> keystorePath;
|
||||
public final Setting<SecureString> keystorePassword;
|
||||
public final Setting<String> keystoreAlgorithm;
|
||||
public final Setting<Optional<String>> keystoreType;
|
||||
public final Setting<SecureString> keystoreKeyPassword;
|
||||
|
||||
public final Setting<Optional<String>> truststorePath;
|
||||
public final Setting<SecureString> truststorePassword;
|
||||
public final Setting<String> truststoreAlgorithm;
|
||||
public final Setting<Optional<String>> truststoreType;
|
||||
public final Setting<Optional<String>> trustRestrictionsPath;
|
||||
public final Setting<Optional<String>> keyPath;
|
||||
public final Setting<SecureString> keyPassword;
|
||||
public final Setting<Optional<String>> cert;
|
||||
public final Setting<List<String>> caPaths;
|
||||
public final Setting<Optional<SSLClientAuth>> clientAuth;
|
||||
public final Setting<Optional<VerificationMode>> verificationMode;
|
||||
|
@ -49,11 +44,6 @@ public class SSLConfigurationSettings {
|
|||
// public for PKI realm
|
||||
public final Setting<SecureString> legacyTruststorePassword;
|
||||
|
||||
// pkg private for tests
|
||||
final Setting<SecureString> legacyKeystorePassword;
|
||||
final Setting<SecureString> legacyKeystoreKeyPassword;
|
||||
final Setting<SecureString> legacyKeyPassword;
|
||||
|
||||
private final List<Setting<?>> allSettings;
|
||||
|
||||
/**
|
||||
|
@ -73,41 +63,28 @@ public class SSLConfigurationSettings {
|
|||
public static final Setting<List<String>> SUPPORTED_PROTOCOLS_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.supported_protocols", SUPPORTED_PROTOCOLS_TEMPLATE) ;
|
||||
|
||||
private static final Function<String, Setting<Optional<String>>> KEYSTORE_PATH_TEMPLATE = key -> new Setting<>(key, s -> null,
|
||||
Optional::ofNullable, Property.NodeScope, Property.Filtered);
|
||||
public static final Setting<Optional<String>> KEYSTORE_PATH_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.keystore.path", KEYSTORE_PATH_TEMPLATE);
|
||||
"xpack.security.ssl.keystore.path", X509KeyPairSettings.KEYSTORE_PATH_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<SecureString>> LEGACY_KEYSTORE_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
|
||||
SecureString::new, Property.Deprecated, Property.Filtered, Property.NodeScope);
|
||||
public static final Setting<SecureString> LEGACY_KEYSTORE_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.keystore.password", LEGACY_KEYSTORE_PASSWORD_TEMPLATE);
|
||||
"xpack.security.ssl.keystore.password", X509KeyPairSettings.LEGACY_KEYSTORE_PASSWORD_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<SecureString>> KEYSTORE_PASSWORD_TEMPLATE = key -> SecureSetting.secureString(key,
|
||||
LEGACY_KEYSTORE_PASSWORD_TEMPLATE.apply(key.replace("keystore.secure_password", "keystore.password")));
|
||||
public static final Setting<SecureString> KEYSTORE_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.keystore.secure_password", KEYSTORE_PASSWORD_TEMPLATE);
|
||||
"xpack.security.ssl.keystore.secure_password", X509KeyPairSettings.KEYSTORE_PASSWORD_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<SecureString>> LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
|
||||
SecureString::new, Property.Deprecated, Property.Filtered, Property.NodeScope);
|
||||
public static final Setting<SecureString> LEGACY_KEYSTORE_KEY_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.keystore.key_password", LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE);
|
||||
"xpack.security.ssl.keystore.key_password", X509KeyPairSettings.LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<SecureString>> KEYSTORE_KEY_PASSWORD_TEMPLATE = key ->
|
||||
SecureSetting.secureString(key, LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(key.replace("keystore.secure_key_password",
|
||||
"keystore.key_password")));
|
||||
public static final Setting<SecureString> KEYSTORE_KEY_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.keystore.secure_key_password", KEYSTORE_KEY_PASSWORD_TEMPLATE);
|
||||
"xpack.security.ssl.keystore.secure_key_password", X509KeyPairSettings.KEYSTORE_KEY_PASSWORD_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<Optional<String>>> TRUST_STORE_PATH_TEMPLATE = key -> new Setting<>(key, s -> null,
|
||||
Optional::ofNullable, Property.NodeScope, Property.Filtered);
|
||||
public static final Setting<Optional<String>> TRUST_STORE_PATH_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.truststore.path", TRUST_STORE_PATH_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<Optional<String>>> KEY_PATH_TEMPLATE = key -> new Setting<>(key, s -> null,
|
||||
Optional::ofNullable, Property.NodeScope, Property.Filtered);
|
||||
public static final Setting<Optional<String>> KEY_PATH_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.key", KEY_PATH_TEMPLATE);
|
||||
"xpack.security.ssl.key", X509KeyPairSettings.KEY_PATH_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<SecureString>> LEGACY_TRUSTSTORE_PASSWORD_TEMPLATE = key ->
|
||||
new Setting<>(key, "", SecureString::new, Property.Deprecated, Property.Filtered, Property.NodeScope);
|
||||
|
@ -120,11 +97,8 @@ public class SSLConfigurationSettings {
|
|||
public static final Setting<SecureString> TRUSTSTORE_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.truststore.secure_password", TRUSTSTORE_PASSWORD_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<String>> KEY_STORE_ALGORITHM_TEMPLATE = key ->
|
||||
new Setting<>(key, s -> KeyManagerFactory.getDefaultAlgorithm(),
|
||||
Function.identity(), Property.NodeScope, Property.Filtered);
|
||||
public static final Setting<String> KEY_STORE_ALGORITHM_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.keystore.algorithm", KEY_STORE_ALGORITHM_TEMPLATE);
|
||||
"xpack.security.ssl.keystore.algorithm", X509KeyPairSettings.KEY_STORE_ALGORITHM_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<String>> TRUST_STORE_ALGORITHM_TEMPLATE = key ->
|
||||
new Setting<>(key, s -> TrustManagerFactory.getDefaultAlgorithm(),
|
||||
|
@ -132,12 +106,11 @@ public class SSLConfigurationSettings {
|
|||
public static final Setting<String> TRUST_STORE_ALGORITHM_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.truststore.algorithm", TRUST_STORE_ALGORITHM_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<Optional<String>>> KEY_STORE_TYPE_TEMPLATE = key ->
|
||||
new Setting<>(key, s -> null, Optional::ofNullable, Property.NodeScope, Property.Filtered);
|
||||
public static final Setting<Optional<String>> KEY_STORE_TYPE_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.keystore.type", KEY_STORE_TYPE_TEMPLATE);
|
||||
"xpack.security.ssl.keystore.type", X509KeyPairSettings.KEY_STORE_TYPE_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<Optional<String>>> TRUST_STORE_TYPE_TEMPLATE = KEY_STORE_TYPE_TEMPLATE;
|
||||
private static final Function<String, Setting<Optional<String>>> TRUST_STORE_TYPE_TEMPLATE =
|
||||
X509KeyPairSettings.KEY_STORE_TYPE_TEMPLATE;
|
||||
public static final Setting<Optional<String>> TRUST_STORE_TYPE_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.truststore.type", TRUST_STORE_TYPE_TEMPLATE);
|
||||
|
||||
|
@ -146,21 +119,14 @@ public class SSLConfigurationSettings {
|
|||
public static final Setting<Optional<String>> TRUST_RESTRICTIONS_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.trust_restrictions", TRUST_RESTRICTIONS_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<SecureString>> LEGACY_KEY_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
|
||||
SecureString::new, Property.Deprecated, Property.Filtered, Property.NodeScope);
|
||||
public static final Setting<SecureString> LEGACY_KEY_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.key_passphrase", LEGACY_KEY_PASSWORD_TEMPLATE);
|
||||
"xpack.security.ssl.key_passphrase", X509KeyPairSettings.LEGACY_KEY_PASSWORD_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<SecureString>> KEY_PASSWORD_TEMPLATE = key ->
|
||||
SecureSetting.secureString(key, LEGACY_KEY_PASSWORD_TEMPLATE.apply(key.replace("secure_key_passphrase",
|
||||
"key_passphrase")));
|
||||
public static final Setting<SecureString> KEY_PASSWORD_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.secure_key_passphrase", KEY_PASSWORD_TEMPLATE);
|
||||
"xpack.security.ssl.secure_key_passphrase", X509KeyPairSettings.KEY_PASSWORD_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<Optional<String>>> CERT_TEMPLATE = key -> new Setting<>(key, s -> null,
|
||||
Optional::ofNullable, Property.NodeScope, Property.Filtered);
|
||||
public static final Setting<Optional<String>> CERT_PROFILES = Setting.affixKeySetting("transport.profiles.",
|
||||
"xpack.security.ssl.certificate", CERT_TEMPLATE);
|
||||
"xpack.security.ssl.certificate", X509KeyPairSettings.CERT_TEMPLATE);
|
||||
|
||||
private static final Function<String, Setting<List<String>>> CAPATH_SETTING_TEMPLATE = key -> Setting.listSetting(key, Collections
|
||||
.emptyList(), Function.identity(), Property.NodeScope, Property.Filtered);
|
||||
|
@ -187,34 +153,25 @@ public class SSLConfigurationSettings {
|
|||
*/
|
||||
private SSLConfigurationSettings(String prefix) {
|
||||
assert prefix != null : "Prefix cannot be null (but can be blank)";
|
||||
|
||||
x509KeyPair = new X509KeyPairSettings(prefix, true);
|
||||
ciphers = CIPHERS_SETTING_TEMPLATE.apply(prefix + "cipher_suites");
|
||||
supportedProtocols = SUPPORTED_PROTOCOLS_TEMPLATE.apply(prefix + "supported_protocols");
|
||||
keystorePath = KEYSTORE_PATH_TEMPLATE.apply(prefix + "keystore.path");
|
||||
legacyKeystorePassword = LEGACY_KEYSTORE_PASSWORD_TEMPLATE.apply(prefix + "keystore.password");
|
||||
keystorePassword = KEYSTORE_PASSWORD_TEMPLATE.apply(prefix + "keystore.secure_password");
|
||||
legacyKeystoreKeyPassword = LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(prefix + "keystore.key_password");
|
||||
keystoreKeyPassword = KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(prefix + "keystore.secure_key_password");
|
||||
truststorePath = TRUST_STORE_PATH_TEMPLATE.apply(prefix + "truststore.path");
|
||||
legacyTruststorePassword = LEGACY_TRUSTSTORE_PASSWORD_TEMPLATE.apply(prefix + "truststore.password");
|
||||
truststorePassword = TRUSTSTORE_PASSWORD_TEMPLATE.apply(prefix + "truststore.secure_password");
|
||||
keystoreAlgorithm = KEY_STORE_ALGORITHM_TEMPLATE.apply(prefix + "keystore.algorithm");
|
||||
truststoreAlgorithm = TRUST_STORE_ALGORITHM_TEMPLATE.apply(prefix + "truststore.algorithm");
|
||||
keystoreType = KEY_STORE_TYPE_TEMPLATE.apply(prefix + "keystore.type");
|
||||
truststoreType = TRUST_STORE_TYPE_TEMPLATE.apply(prefix + "truststore.type");
|
||||
trustRestrictionsPath = TRUST_RESTRICTIONS_TEMPLATE.apply(prefix + "trust_restrictions.path");
|
||||
keyPath = KEY_PATH_TEMPLATE.apply(prefix + "key");
|
||||
legacyKeyPassword = LEGACY_KEY_PASSWORD_TEMPLATE.apply(prefix + "key_passphrase");
|
||||
keyPassword = KEY_PASSWORD_TEMPLATE.apply(prefix + "secure_key_passphrase");
|
||||
cert = CERT_TEMPLATE.apply(prefix + "certificate");
|
||||
caPaths = CAPATH_SETTING_TEMPLATE.apply(prefix + "certificate_authorities");
|
||||
clientAuth = CLIENT_AUTH_SETTING_TEMPLATE.apply(prefix + "client_authentication");
|
||||
verificationMode = VERIFICATION_MODE_SETTING_TEMPLATE.apply(prefix + "verification_mode");
|
||||
|
||||
this.allSettings = Arrays.asList(ciphers, supportedProtocols,
|
||||
keystorePath, keystorePassword, keystoreAlgorithm, keystoreType, keystoreKeyPassword,
|
||||
final List<Setting<? extends Object>> settings = CollectionUtils.arrayAsArrayList(ciphers, supportedProtocols,
|
||||
truststorePath, truststorePassword, truststoreAlgorithm, truststoreType, trustRestrictionsPath,
|
||||
keyPath, keyPassword, cert, caPaths, clientAuth, verificationMode,
|
||||
legacyKeystorePassword, legacyKeystoreKeyPassword, legacyKeyPassword, legacyTruststorePassword);
|
||||
caPaths, clientAuth, verificationMode, legacyTruststorePassword);
|
||||
settings.addAll(x509KeyPair.getAllSettings());
|
||||
this.allSettings = Collections.unmodifiableList(settings);
|
||||
}
|
||||
|
||||
public static String getKeyStoreType(Setting<Optional<String>> setting, Settings settings, String path) {
|
||||
|
@ -262,4 +219,5 @@ public class SSLConfigurationSettings {
|
|||
KEY_PATH_PROFILES, LEGACY_KEY_PASSWORD_PROFILES, KEY_PASSWORD_PROFILES,CERT_PROFILES,CAPATH_SETTING_PROFILES,
|
||||
CLIENT_AUTH_SETTING_PROFILES, VERIFICATION_MODE_SETTING_PROFILES);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.ssl;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import java.security.KeyStore;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.settings.SecureSetting;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.CollectionUtils;
|
||||
import org.elasticsearch.env.Environment;
|
||||
|
||||
import static org.elasticsearch.xpack.ssl.SSLConfigurationSettings.getKeyStoreType;
|
||||
|
||||
/**
|
||||
* An encapsulation of the configuration options for X.509 Key Pair support in X-Pack security.
|
||||
* The most common use is as the private key and associated certificate for SSL/TLS support, but it can also be used for providing
|
||||
* signing or encryption keys (if they are X.509 based).
|
||||
* This class supports using a {@link java.security.KeyStore} (with configurable {@link KeyStore#getType() type}) or PEM based files.
|
||||
*/
|
||||
public class X509KeyPairSettings {
|
||||
|
||||
static final Function<String, Setting<Optional<String>>> KEYSTORE_PATH_TEMPLATE = key -> new Setting<>(key, s -> null,
|
||||
Optional::ofNullable, Setting.Property.NodeScope, Setting.Property.Filtered);
|
||||
|
||||
static final Function<String, Setting<SecureString>> LEGACY_KEYSTORE_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
|
||||
SecureString::new, Setting.Property.Deprecated, Setting.Property.Filtered, Setting.Property.NodeScope);
|
||||
static final Function<String, Setting<SecureString>> KEYSTORE_PASSWORD_TEMPLATE = key -> SecureSetting.secureString(key,
|
||||
LEGACY_KEYSTORE_PASSWORD_TEMPLATE.apply(key.replace("keystore.secure_password", "keystore.password")));
|
||||
|
||||
static final Function<String, Setting<String>> KEY_STORE_ALGORITHM_TEMPLATE = key ->
|
||||
new Setting<>(key, s -> KeyManagerFactory.getDefaultAlgorithm(),
|
||||
Function.identity(), Setting.Property.NodeScope, Setting.Property.Filtered);
|
||||
|
||||
static final Function<String, Setting<Optional<String>>> KEY_STORE_TYPE_TEMPLATE = key ->
|
||||
new Setting<>(key, s -> null, Optional::ofNullable, Setting.Property.NodeScope, Setting.Property.Filtered);
|
||||
|
||||
static final Function<String, Setting<SecureString>> LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
|
||||
SecureString::new, Setting.Property.Deprecated, Setting.Property.Filtered, Setting.Property.NodeScope);
|
||||
static final Function<String, Setting<SecureString>> KEYSTORE_KEY_PASSWORD_TEMPLATE = key ->
|
||||
SecureSetting.secureString(key, LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(key.replace("keystore.secure_key_password",
|
||||
"keystore.key_password")));
|
||||
|
||||
static final Function<String, Setting<Optional<String>>> KEY_PATH_TEMPLATE = key -> new Setting<>(key, s -> null,
|
||||
Optional::ofNullable, Setting.Property.NodeScope, Setting.Property.Filtered);
|
||||
|
||||
static final Function<String, Setting<Optional<String>>> CERT_TEMPLATE = key -> new Setting<>(key, s -> null,
|
||||
Optional::ofNullable, Setting.Property.NodeScope, Setting.Property.Filtered);
|
||||
|
||||
static final Function<String, Setting<SecureString>> LEGACY_KEY_PASSWORD_TEMPLATE = key -> new Setting<>(key, "",
|
||||
SecureString::new, Setting.Property.Deprecated, Setting.Property.Filtered, Setting.Property.NodeScope);
|
||||
static final Function<String, Setting<SecureString>> KEY_PASSWORD_TEMPLATE = key ->
|
||||
SecureSetting.secureString(key, LEGACY_KEY_PASSWORD_TEMPLATE.apply(key.replace("secure_key_passphrase",
|
||||
"key_passphrase")));
|
||||
|
||||
|
||||
private final String prefix;
|
||||
|
||||
// Specify private cert/key pair via keystore
|
||||
final Setting<Optional<String>> keystorePath;
|
||||
final Setting<SecureString> keystorePassword;
|
||||
final Setting<String> keystoreAlgorithm;
|
||||
final Setting<Optional<String>> keystoreType;
|
||||
final Setting<SecureString> keystoreKeyPassword;
|
||||
|
||||
// Specify private cert/key pair via key and certificate files
|
||||
final Setting<Optional<String>> keyPath;
|
||||
final Setting<SecureString> keyPassword;
|
||||
final Setting<Optional<String>> certificatePath;
|
||||
|
||||
// Optional support for legacy (non secure) passwords
|
||||
// pkg private for tests
|
||||
final Setting<SecureString> legacyKeystorePassword;
|
||||
final Setting<SecureString> legacyKeystoreKeyPassword;
|
||||
final Setting<SecureString> legacyKeyPassword;
|
||||
|
||||
private final List<Setting<?>> allSettings;
|
||||
|
||||
public X509KeyPairSettings(String prefix, boolean acceptNonSecurePasswords) {
|
||||
keystorePath = KEYSTORE_PATH_TEMPLATE.apply(prefix + "keystore.path");
|
||||
keystorePassword = KEYSTORE_PASSWORD_TEMPLATE.apply(prefix + "keystore.secure_password");
|
||||
keystoreAlgorithm = KEY_STORE_ALGORITHM_TEMPLATE.apply(prefix + "keystore.algorithm");
|
||||
keystoreType = KEY_STORE_TYPE_TEMPLATE.apply(prefix + "keystore.type");
|
||||
keystoreKeyPassword = KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(prefix + "keystore.secure_key_password");
|
||||
|
||||
keyPath = KEY_PATH_TEMPLATE.apply(prefix + "key");
|
||||
keyPassword = KEY_PASSWORD_TEMPLATE.apply(prefix + "secure_key_passphrase");
|
||||
certificatePath = CERT_TEMPLATE.apply(prefix + "certificate");
|
||||
|
||||
legacyKeystorePassword = LEGACY_KEYSTORE_PASSWORD_TEMPLATE.apply(prefix + "keystore.password");
|
||||
legacyKeystoreKeyPassword = LEGACY_KEYSTORE_KEY_PASSWORD_TEMPLATE.apply(prefix + "keystore.key_password");
|
||||
legacyKeyPassword = LEGACY_KEY_PASSWORD_TEMPLATE.apply(prefix + "key_passphrase");
|
||||
this.prefix = prefix;
|
||||
|
||||
final List<Setting<?>> settings = CollectionUtils.arrayAsArrayList(
|
||||
keystorePath, keystorePassword, keystoreAlgorithm, keystoreType, keystoreKeyPassword,
|
||||
keyPath, keyPassword, certificatePath);
|
||||
if (acceptNonSecurePasswords) {
|
||||
settings.add(legacyKeystorePassword);
|
||||
settings.add(legacyKeystoreKeyPassword);
|
||||
settings.add(legacyKeyPassword);
|
||||
}
|
||||
allSettings = Collections.unmodifiableList(settings);
|
||||
}
|
||||
|
||||
|
||||
public Collection<? extends Setting<?>> getAllSettings() {
|
||||
return allSettings;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
}
|
|
@ -28,6 +28,14 @@ grant {
|
|||
permission java.io.FilePermission "\\\\.\\pipe\\*", "read,write";
|
||||
};
|
||||
|
||||
grant codeBase "${codebase.xmlsec-2.0.8.jar}" {
|
||||
// needed during initialization of OpenSAML library where xml security algorithms are registered
|
||||
// see https://github.com/apache/santuario-java/blob/e79f1fe4192de73a975bc7246aee58ed0703343d/src/main/java/org/apache/xml/security/utils/JavaUtils.java#L205-L220
|
||||
// and https://git.shibboleth.net/view/?p=java-opensaml.git;a=blob;f=opensaml-xmlsec-impl/src/main/java/org/opensaml/xmlsec/signature/impl/SignatureMarshaller.java;hb=db0eaa64210f0e32d359cd6c57bedd57902bf811#l52
|
||||
// which uses it in the opensaml-xmlsec-impl
|
||||
permission java.security.SecurityPermission "org.apache.xml.security.register";
|
||||
};
|
||||
|
||||
grant codeBase "${codebase.netty-common}" {
|
||||
// for reading the system-wide configuration for the backlog of established sockets
|
||||
permission java.io.FilePermission "/proc/sys/net/core/somaxconn", "read";
|
||||
|
|
|
@ -170,6 +170,9 @@
|
|||
},
|
||||
"invalidated" : {
|
||||
"type" : "boolean"
|
||||
},
|
||||
"realm" : {
|
||||
"type" : "keyword"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ package org.elasticsearch.test;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.hamcrest.CustomMatcher;
|
||||
import org.hamcrest.Matcher;
|
||||
|
@ -23,4 +25,25 @@ public class TestMatchers extends Matchers {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<String> matchesPattern(String regex) {
|
||||
return matchesPattern(Pattern.compile(regex));
|
||||
}
|
||||
|
||||
public static Matcher<String> matchesPattern(Pattern pattern) {
|
||||
return predicate("Matches " + pattern.pattern(), String.class, pattern.asPredicate());
|
||||
}
|
||||
|
||||
private static <T> Matcher<T> predicate(String description, Class<T> type, Predicate<T> stringPredicate) {
|
||||
return new CustomMatcher<T>(description) {
|
||||
@Override
|
||||
public boolean matches(Object item) {
|
||||
if (type.isInstance(item)) {
|
||||
return stringPredicate.test(type.cast(item));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,13 +45,13 @@ public class SSLConfigurationSettingsTests extends ESTestCase {
|
|||
|
||||
public void testParseKeystoreAlgorithmWithPrefix() {
|
||||
final SSLConfigurationSettings ssl = SSLConfigurationSettings.withPrefix("xpack.security.authc.realms.ldap1.ssl.");
|
||||
assertThat(ssl.keystoreAlgorithm.match("xpack.security.authc.realms.ldap1.ssl.keystore.algorithm"), is(true));
|
||||
assertThat(ssl.x509KeyPair.keystoreAlgorithm.match("xpack.security.authc.realms.ldap1.ssl.keystore.algorithm"), is(true));
|
||||
|
||||
final String algo = randomAlphaOfLength(16);
|
||||
final Settings settings = Settings.builder()
|
||||
.put("xpack.security.authc.realms.ldap1.ssl.keystore.algorithm", algo)
|
||||
.build();
|
||||
assertThat(ssl.keystoreAlgorithm.get(settings), is(algo));
|
||||
assertThat(ssl.x509KeyPair.keystoreAlgorithm.get(settings), is(algo));
|
||||
}
|
||||
|
||||
public void testParseProtocolsListWithPrefix() {
|
||||
|
@ -68,16 +68,16 @@ public class SSLConfigurationSettingsTests extends ESTestCase {
|
|||
final SSLConfigurationSettings ssl = SSLConfigurationSettings.withoutPrefix();
|
||||
final Settings settings = Settings.EMPTY;
|
||||
assertThat(ssl.caPaths.get(settings).size(), is(0));
|
||||
assertThat(ssl.cert.get(settings).isPresent(), is(false));
|
||||
assertThat(ssl.x509KeyPair.certificatePath.get(settings).isPresent(), is(false));
|
||||
assertThat(ssl.ciphers.get(settings).size(), is(0));
|
||||
assertThat(ssl.clientAuth.get(settings).isPresent(), is(false));
|
||||
assertThat(ssl.keyPassword.exists(settings), is(false));
|
||||
assertThat(ssl.keyPath.get(settings).isPresent(), is(false));
|
||||
assertThat(ssl.keystoreAlgorithm.get(settings), is(KeyManagerFactory.getDefaultAlgorithm()));
|
||||
assertThat(ssl.keystoreType.get(settings).isPresent(), is(false));
|
||||
assertThat(ssl.keystoreKeyPassword.exists(settings), is(false));
|
||||
assertThat(ssl.keystorePassword.exists(settings), is(false));
|
||||
assertThat(ssl.keystorePath.get(settings).isPresent(), is(false));
|
||||
assertThat(ssl.x509KeyPair.keyPassword.exists(settings), is(false));
|
||||
assertThat(ssl.x509KeyPair.keyPath.get(settings).isPresent(), is(false));
|
||||
assertThat(ssl.x509KeyPair.keystoreAlgorithm.get(settings), is(KeyManagerFactory.getDefaultAlgorithm()));
|
||||
assertThat(ssl.x509KeyPair.keystoreType.get(settings).isPresent(), is(false));
|
||||
assertThat(ssl.x509KeyPair.keystoreKeyPassword.exists(settings), is(false));
|
||||
assertThat(ssl.x509KeyPair.keystorePassword.exists(settings), is(false));
|
||||
assertThat(ssl.x509KeyPair.keystorePath.get(settings).isPresent(), is(false));
|
||||
assertThat(ssl.supportedProtocols.get(settings).size(), is(0));
|
||||
assertThat(ssl.truststoreAlgorithm.get(settings), is(TrustManagerFactory.getDefaultAlgorithm()));
|
||||
assertThat(ssl.truststoreType.get(settings).isPresent(), is(false));
|
||||
|
@ -86,7 +86,7 @@ public class SSLConfigurationSettingsTests extends ESTestCase {
|
|||
assertThat(ssl.trustRestrictionsPath.get(settings).isPresent(), is(false));
|
||||
assertThat(ssl.verificationMode.get(settings).isPresent(), is(false));
|
||||
|
||||
assertThat(SSLConfigurationSettings.getKeyStoreType(ssl.keystoreType, settings, null), is("jks"));
|
||||
assertThat(SSLConfigurationSettings.getKeyStoreType(ssl.x509KeyPair.keystoreType, settings, null), is("jks"));
|
||||
assertThat(SSLConfigurationSettings.getKeyStoreType(ssl.truststoreType, settings, null), is("jks"));
|
||||
}
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ public class SSLConfigurationTests extends ESTestCase {
|
|||
assertThat(ksKeyInfo.keyStorePassword, is(equalTo("password")));
|
||||
assertThat(ksKeyInfo.keyPassword, is(equalTo("password")));
|
||||
assertSettingDeprecationsAndWarnings(new Setting<?>[] {
|
||||
SSLConfiguration.SETTINGS_PARSER.legacyKeystorePassword});
|
||||
SSLConfiguration.SETTINGS_PARSER.x509KeyPair.legacyKeystorePassword});
|
||||
}
|
||||
|
||||
public void testKeystoreKeyPassword() {
|
||||
|
@ -122,7 +122,9 @@ public class SSLConfigurationTests extends ESTestCase {
|
|||
assertThat(ksKeyInfo.keyStorePassword, is(equalTo("password")));
|
||||
assertThat(ksKeyInfo.keyPassword, is(equalTo("keypass")));
|
||||
assertSettingDeprecationsAndWarnings(new Setting<?>[] {
|
||||
SSLConfiguration.SETTINGS_PARSER.legacyKeystorePassword, SSLConfiguration.SETTINGS_PARSER.legacyKeystoreKeyPassword});
|
||||
SSLConfiguration.SETTINGS_PARSER.x509KeyPair.legacyKeystorePassword,
|
||||
SSLConfiguration.SETTINGS_PARSER.x509KeyPair.legacyKeystoreKeyPassword
|
||||
});
|
||||
}
|
||||
|
||||
public void testInferKeystoreTypeFromJksFile() {
|
||||
|
@ -332,7 +334,7 @@ public class SSLConfigurationTests extends ESTestCase {
|
|||
assertNotNull(keyManager);
|
||||
assertThat(config.trustConfig(), instanceOf(CombiningTrustConfig.class));
|
||||
assertCombiningTrustConfigContainsCorrectIssuers(config);
|
||||
assertSettingDeprecationsAndWarnings(new Setting<?>[] {SSLConfiguration.SETTINGS_PARSER.legacyKeyPassword});
|
||||
assertSettingDeprecationsAndWarnings(new Setting<?>[] {SSLConfiguration.SETTINGS_PARSER.x509KeyPair.legacyKeyPassword});
|
||||
}
|
||||
|
||||
public void testPEMKeyAndTrustFiles() {
|
||||
|
@ -381,7 +383,7 @@ public class SSLConfigurationTests extends ESTestCase {
|
|||
assertThat(config.trustConfig(), instanceOf(PEMTrustConfig.class));
|
||||
TrustManager trustManager = keyConfig.createTrustManager(env);
|
||||
assertNotNull(trustManager);
|
||||
assertSettingDeprecationsAndWarnings(new Setting<?>[] {SSLConfiguration.SETTINGS_PARSER.legacyKeyPassword});
|
||||
assertSettingDeprecationsAndWarnings(new Setting<?>[] {SSLConfiguration.SETTINGS_PARSER.x509KeyPair.legacyKeyPassword});
|
||||
}
|
||||
|
||||
private void assertCombiningTrustConfigContainsCorrectIssuers(SSLConfiguration sslConfiguration) {
|
||||
|
|
|
@ -25,7 +25,10 @@ dependencies {
|
|||
provided project(path: ':plugins:transport-nio', configuration: 'runtime')
|
||||
|
||||
testCompile project(path: ':x-pack-elasticsearch:plugin:monitoring')
|
||||
testCompile project(path: ':x-pack-elasticsearch:plugin:watcher')
|
||||
testCompile ( project(path: ':x-pack-elasticsearch:plugin:watcher') ) {
|
||||
// Shouldn't have a dependency on watcher at all, but since we do, drop guava
|
||||
exclude group: "com.google.guava", module: "guava"
|
||||
}
|
||||
|
||||
testCompile project(path: ':x-pack-elasticsearch:plugin:core', configuration: 'testArtifacts')
|
||||
|
||||
|
@ -33,10 +36,38 @@ dependencies {
|
|||
compile 'com.unboundid:unboundid-ldapsdk:3.2.0'
|
||||
compile 'org.bouncycastle:bcprov-jdk15on:1.58'
|
||||
compile 'org.bouncycastle:bcpkix-jdk15on:1.58'
|
||||
|
||||
// the following are all SAML dependencies - might as well download the whole internet
|
||||
compile "org.opensaml:opensaml-core:3.3.0"
|
||||
compile "org.opensaml:opensaml-saml-api:3.3.0"
|
||||
compile "org.opensaml:opensaml-saml-impl:3.3.0"
|
||||
compile "org.opensaml:opensaml-messaging-api:3.3.0"
|
||||
compile "org.opensaml:opensaml-messaging-impl:3.3.0"
|
||||
compile "org.opensaml:opensaml-security-api:3.3.0"
|
||||
compile "org.opensaml:opensaml-security-impl:3.3.0"
|
||||
compile "org.opensaml:opensaml-profile-api:3.3.0"
|
||||
compile "org.opensaml:opensaml-profile-impl:3.3.0"
|
||||
compile "org.opensaml:opensaml-xmlsec-api:3.3.0"
|
||||
compile "org.opensaml:opensaml-xmlsec-impl:3.3.0"
|
||||
compile "org.opensaml:opensaml-soap-api:3.3.0"
|
||||
compile "org.opensaml:opensaml-soap-impl:3.3.0"
|
||||
compile "org.opensaml:opensaml-storage-api:3.3.0"
|
||||
compile "org.opensaml:opensaml-storage-impl:3.3.0"
|
||||
compile "net.shibboleth.utilities:java-support:7.3.0"
|
||||
compile "org.apache.santuario:xmlsec:2.0.8"
|
||||
compile "io.dropwizard.metrics:metrics-core:3.2.2"
|
||||
compile "org.cryptacular:cryptacular:1.2.0"
|
||||
compile "org.slf4j:slf4j-api:${versions.slf4j}"
|
||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}"
|
||||
compile "org.apache.httpcomponents:httpclient:${versions.httpclient}"
|
||||
compile "org.apache.httpcomponents:httpcore:${versions.httpcore}"
|
||||
compile "org.apache.httpcomponents:httpasyncclient:${versions.httpasyncclient}"
|
||||
compile "org.apache.httpcomponents:httpcore-nio:${versions.httpcore}"
|
||||
compile "org.apache.httpcomponents:httpclient-cache:${versions.httpclient}"
|
||||
compile 'com.google.guava:guava:19.0'
|
||||
|
||||
testCompile 'org.elasticsearch:securemock:1.2'
|
||||
testCompile "org.elasticsearch:mocksocket:${versions.mocksocket}"
|
||||
testCompile 'org.slf4j:slf4j-log4j12:1.6.2'
|
||||
testCompile 'org.slf4j:slf4j-api:1.6.2'
|
||||
//testCompile "org.yaml:snakeyaml:${versions.snakeyaml}"
|
||||
|
||||
}
|
||||
|
@ -68,6 +99,8 @@ dependencyLicenses {
|
|||
mapping from: /netty-.*/, to: 'netty'
|
||||
mapping from: /bc.*/, to: 'bouncycastle'
|
||||
mapping from: /transport-netty.*/, to: 'elasticsearch'
|
||||
mapping from: /java-support|opensaml-.*/, to: 'shibboleth'
|
||||
mapping from: /http.*/, to: 'httpclient'
|
||||
ignoreSha 'x-pack-core'
|
||||
ignoreSha 'transport-netty4'
|
||||
}
|
||||
|
@ -81,6 +114,121 @@ forbiddenPatterns {
|
|||
|
||||
forbiddenApisMain {
|
||||
signaturesURLs += file('forbidden/ldap-signatures.txt').toURI().toURL()
|
||||
signaturesURLs += file('forbidden/xml-signatures.txt').toURI().toURL()
|
||||
}
|
||||
|
||||
// classes are missing, e.g. com.ibm.icu.lang.UCharacter
|
||||
thirdPartyAudit.excludes = [
|
||||
// SAML dependencies
|
||||
// [missing classes] Some cli utilities that we don't use depend on these missing JCommander classes
|
||||
'com.beust.jcommander.JCommander',
|
||||
'com.beust.jcommander.converters.BaseConverter',
|
||||
// [missing classes] Shibboleth + OpenSAML have servlet support that we don't use
|
||||
'javax.servlet.AsyncContext',
|
||||
'javax.servlet.DispatcherType',
|
||||
'javax.servlet.Filter',
|
||||
'javax.servlet.FilterChain',
|
||||
'javax.servlet.FilterConfig',
|
||||
'javax.servlet.RequestDispatcher',
|
||||
'javax.servlet.ServletContext',
|
||||
'javax.servlet.ServletException',
|
||||
'javax.servlet.ServletInputStream',
|
||||
'javax.servlet.ServletOutputStream',
|
||||
'javax.servlet.ServletRequest',
|
||||
'javax.servlet.ServletResponse',
|
||||
'javax.servlet.http.Cookie',
|
||||
'javax.servlet.http.HttpServletRequest',
|
||||
'javax.servlet.http.HttpServletResponse',
|
||||
'javax.servlet.http.HttpServletResponseWrapper',
|
||||
'javax.servlet.http.HttpSession',
|
||||
'javax.servlet.http.Part',
|
||||
// [missing classes] Shibboleth + OpenSAML have velocity support that we don't use
|
||||
'org.apache.velocity.VelocityContext',
|
||||
'org.apache.velocity.app.VelocityEngine',
|
||||
'org.apache.velocity.context.Context',
|
||||
'org.apache.velocity.exception.VelocityException',
|
||||
'org.apache.velocity.runtime.RuntimeServices',
|
||||
'org.apache.velocity.runtime.log.LogChute',
|
||||
'org.apache.velocity.runtime.resource.loader.StringResourceLoader',
|
||||
'org.apache.velocity.runtime.resource.util.StringResourceRepository',
|
||||
// [missing classes] OpenSAML depends on Apache XML security which depends on Xalan, but only for functionality that OpenSAML doesn't use
|
||||
'org.apache.xml.dtm.DTM',
|
||||
'org.apache.xml.utils.PrefixResolver',
|
||||
'org.apache.xml.utils.PrefixResolverDefault',
|
||||
'org.apache.xpath.Expression',
|
||||
'org.apache.xpath.NodeSetDTM',
|
||||
'org.apache.xpath.XPath',
|
||||
'org.apache.xpath.XPathContext',
|
||||
'org.apache.xpath.compiler.FunctionTable',
|
||||
'org.apache.xpath.functions.Function',
|
||||
'org.apache.xpath.objects.XNodeSet',
|
||||
'org.apache.xpath.objects.XObject',
|
||||
// [missing classes] OpenSAML storage has an optional LDAP storage impl
|
||||
'org.ldaptive.AttributeModification',
|
||||
'org.ldaptive.AttributeModificationType',
|
||||
'org.ldaptive.Connection',
|
||||
'org.ldaptive.DeleteOperation',
|
||||
'org.ldaptive.DeleteRequest',
|
||||
'org.ldaptive.LdapAttribute',
|
||||
'org.ldaptive.LdapEntry',
|
||||
'org.ldaptive.LdapException',
|
||||
'org.ldaptive.ModifyOperation',
|
||||
'org.ldaptive.ModifyRequest',
|
||||
'org.ldaptive.Response',
|
||||
'org.ldaptive.ResultCode',
|
||||
'org.ldaptive.SearchOperation',
|
||||
'org.ldaptive.SearchRequest',
|
||||
'org.ldaptive.SearchResult',
|
||||
'org.ldaptive.ext.MergeOperation',
|
||||
'org.ldaptive.ext.MergeRequest',
|
||||
'org.ldaptive.pool.ConnectionPool',
|
||||
'org.ldaptive.pool.PooledConnectionFactory',
|
||||
// [missing classes] OpenSAML storage has an optional JSON-backed storage impl
|
||||
'javax.json.Json',
|
||||
'javax.json.JsonException',
|
||||
'javax.json.JsonNumber',
|
||||
'javax.json.JsonObject',
|
||||
'javax.json.JsonReader',
|
||||
'javax.json.JsonValue$ValueType',
|
||||
'javax.json.JsonValue',
|
||||
'javax.json.stream.JsonGenerator',
|
||||
// [missing classes] OpenSAML storage has an optional JPA storage impl
|
||||
'javax.persistence.EntityManager',
|
||||
'javax.persistence.EntityManagerFactory',
|
||||
'javax.persistence.EntityTransaction',
|
||||
'javax.persistence.LockModeType',
|
||||
'javax/persistence/Query',
|
||||
// [missing classes] OpenSAML storage and HttpClient cache have optional memcache support
|
||||
'net.spy.memcached.CASResponse',
|
||||
'net.spy.memcached.CASValue',
|
||||
'net.spy.memcached.MemcachedClient',
|
||||
'net.spy.memcached.MemcachedClientIF',
|
||||
'net.spy.memcached.CachedData',
|
||||
'net.spy.memcached.internal.OperationFuture',
|
||||
'net.spy.memcached.transcoders.Transcoder',
|
||||
// [missing classes] Http Client cache has optional ehcache support
|
||||
'net.sf.ehcache.Ehcache',
|
||||
'net.sf.ehcache.Element',
|
||||
// [missing classes] SLF4j includes an optional class that depends on an extension class (!)
|
||||
'org.slf4j.ext.EventData',
|
||||
// Guava uses internal java api: sun.misc.Unsafe
|
||||
'com.google.common.cache.Striped64',
|
||||
'com.google.common.cache.Striped64$1',
|
||||
'com.google.common.cache.Striped64$Cell',
|
||||
'com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator',
|
||||
'com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator$1',
|
||||
'com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper',
|
||||
'com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper$1',
|
||||
]
|
||||
|
||||
if (JavaVersion.current() > JavaVersion.VERSION_1_8) {
|
||||
thirdPartyAudit.excludes += [
|
||||
'javax.xml.bind.JAXBContext',
|
||||
'javax.xml.bind.JAXBElement',
|
||||
'javax.xml.bind.JAXBException',
|
||||
'javax.xml.bind.Unmarshaller',
|
||||
'javax.xml.bind.UnmarshallerHandler',
|
||||
];
|
||||
}
|
||||
|
||||
run {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
@defaultMessage DocumentBuilderFactory should not be used directly. Use SamlUtils#getHardenedDocumentBuilder(java.lang.String[]) instead.
|
||||
javax.xml.parsers.DocumentBuilderFactory#newInstance()
|
||||
javax.xml.parsers.DocumentBuilderFactory#newInstance(java.lang.String, java.lang.ClassLoader)
|
||||
|
||||
@defaultMessage TransformerFactory should not be used directly. Use SamlUtils#getHardenedXMLTransformer() instead.
|
||||
javax.xml.transform.TransformerFactory#newInstance()
|
||||
javax.xml.transform.TransformerFactory#newInstance(java.lang.String, java.lang.ClassLoader)
|
||||
|
|
@ -0,0 +1 @@
|
|||
94f6cb97d7f7487a183f283ae80c6e61c86156e3
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed 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.
|
|
@ -0,0 +1,710 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="dns-prefetch" href="https://assets-cdn.github.com">
|
||||
<link rel="dns-prefetch" href="https://avatars0.githubusercontent.com">
|
||||
<link rel="dns-prefetch" href="https://avatars1.githubusercontent.com">
|
||||
<link rel="dns-prefetch" href="https://avatars2.githubusercontent.com">
|
||||
<link rel="dns-prefetch" href="https://avatars3.githubusercontent.com">
|
||||
<link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
|
||||
<link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">
|
||||
|
||||
|
||||
|
||||
<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/frameworks-2d2d4c150f7000385741c6b992b302689ecd172246c6428904e0813be9bceca6.css" media="all" rel="stylesheet" />
|
||||
<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/github-0522ae8d3b3bdc841d2f91f90efd5f1fd9040d910905674cd134ced43a6dfea6.css" media="all" rel="stylesheet" />
|
||||
|
||||
|
||||
<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/site-cfab053e93f0e27f4c63d4ff6b7957bd25f711667fe678e747f8a4d88c47b38d.css" media="all" rel="stylesheet" />
|
||||
|
||||
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<title>cryptacular/NOTICE at master · vt-middleware/cryptacular · GitHub</title>
|
||||
<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">
|
||||
<link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
|
||||
<meta property="fb:app_id" content="1401488693436528">
|
||||
|
||||
|
||||
<meta content="https://avatars7.githubusercontent.com/u/6122907?v=4&s=400" property="og:image" /><meta content="GitHub" property="og:site_name" /><meta content="object" property="og:type" /><meta content="vt-middleware/cryptacular" property="og:title" /><meta content="https://github.com/vt-middleware/cryptacular" property="og:url" /><meta content="cryptacular - The friendly complement to the BouncyCastle crypto API for Java." property="og:description" />
|
||||
|
||||
<link rel="assets" href="https://assets-cdn.github.com/">
|
||||
|
||||
<meta name="pjax-timeout" content="1000">
|
||||
|
||||
<meta name="request-id" content="E0E4:26F16:12A5AE6:1D11801:596C7978" data-pjax-transient>
|
||||
|
||||
|
||||
<meta name="selected-link" value="repo_source" data-pjax-transient>
|
||||
|
||||
<meta name="google-site-verification" content="KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU">
|
||||
<meta name="google-site-verification" content="ZzhVyEFwb7w3e0-uOTltm8Jsck2F5StVihD0exw2fsA">
|
||||
<meta name="google-analytics" content="UA-3769691-2">
|
||||
|
||||
<meta content="collector.githubapp.com" name="octolytics-host" /><meta content="github" name="octolytics-app-id" /><meta content="https://collector.githubapp.com/github-external/browser_event" name="octolytics-event-url" /><meta content="E0E4:26F16:12A5AE6:1D11801:596C7978" name="octolytics-dimension-request_id" /><meta content="sea" name="octolytics-dimension-region_edge" /><meta content="iad" name="octolytics-dimension-region_render" />
|
||||
<meta content="/<user-name>/<repo-name>/blob/show" data-pjax-transient="true" name="analytics-location" />
|
||||
|
||||
|
||||
|
||||
|
||||
<meta class="js-ga-set" name="dimension1" content="Logged Out">
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="hostname" content="github.com">
|
||||
<meta name="user-login" content="">
|
||||
|
||||
<meta name="expected-hostname" content="github.com">
|
||||
<meta name="js-proxy-site-detection-payload" content="N2ZhMjk0NTA4MjI1NmZhYTVlNzM5NzVjZmFkOWY2NGFkNmMxYzcyMGViNzAzZGQxMGMzZmJhZDQ3YWZiZTI0OHx7InJlbW90ZV9hZGRyZXNzIjoiMTEwLjIwLjIyMC4xMzUiLCJyZXF1ZXN0X2lkIjoiRTBFNDoyNkYxNjoxMkE1QUU2OjFEMTE4MDE6NTk2Qzc5NzgiLCJ0aW1lc3RhbXAiOjE1MDAyODEyMDgsImhvc3QiOiJnaXRodWIuY29tIn0=">
|
||||
|
||||
|
||||
<meta name="html-safe-nonce" content="5aa226b80a18dc40894e1d405e4eb31cfca7d616">
|
||||
|
||||
<meta http-equiv="x-pjax-version" content="f682644ce1bb9629b9d9d9bedf64801b">
|
||||
|
||||
|
||||
<link href="https://github.com/vt-middleware/cryptacular/commits/master.atom" rel="alternate" title="Recent Commits to cryptacular:master" type="application/atom+xml">
|
||||
|
||||
<meta name="description" content="cryptacular - The friendly complement to the BouncyCastle crypto API for Java.">
|
||||
<meta name="go-import" content="github.com/vt-middleware/cryptacular git https://github.com/vt-middleware/cryptacular.git">
|
||||
|
||||
<meta content="6122907" name="octolytics-dimension-user_id" /><meta content="vt-middleware" name="octolytics-dimension-user_login" /><meta content="15714989" name="octolytics-dimension-repository_id" /><meta content="vt-middleware/cryptacular" name="octolytics-dimension-repository_nwo" /><meta content="true" name="octolytics-dimension-repository_public" /><meta content="false" name="octolytics-dimension-repository_is_fork" /><meta content="15714989" name="octolytics-dimension-repository_network_root_id" /><meta content="vt-middleware/cryptacular" name="octolytics-dimension-repository_network_root_nwo" /><meta content="false" name="octolytics-dimension-repository_explore_github_marketplace_ci_cta_shown" />
|
||||
|
||||
|
||||
<link rel="canonical" href="https://github.com/vt-middleware/cryptacular/blob/master/NOTICE" data-pjax-transient>
|
||||
|
||||
|
||||
<meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">
|
||||
|
||||
<meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">
|
||||
|
||||
<link rel="mask-icon" href="https://assets-cdn.github.com/pinned-octocat.svg" color="#000000">
|
||||
<link rel="icon" type="image/x-icon" href="https://assets-cdn.github.com/favicon.ico">
|
||||
|
||||
<meta name="theme-color" content="#1e2327">
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body class="logged-out env-production page-blob">
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="position-relative js-header-wrapper ">
|
||||
<a href="#start-of-content" tabindex="1" class="px-2 py-4 show-on-focus js-skip-to-content">Skip to content</a>
|
||||
<div id="js-pjax-loader-bar" class="pjax-loader-bar"><div class="progress"></div></div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<header class="site-header js-details-container Details" role="banner">
|
||||
<div class="site-nav-container">
|
||||
<a class="header-logo-invertocat" href="https://github.com/" aria-label="Homepage" data-ga-click="(Logged out) Header, go to homepage, icon:logo-wordmark">
|
||||
<svg aria-hidden="true" class="octicon octicon-mark-github" height="32" version="1.1" viewBox="0 0 16 16" width="32"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
|
||||
</a>
|
||||
|
||||
<button class="btn-link float-right site-header-toggle js-details-target" type="button" aria-label="Toggle navigation" aria-expanded="false">
|
||||
<svg aria-hidden="true" class="octicon octicon-three-bars" height="24" version="1.1" viewBox="0 0 12 16" width="18"><path fill-rule="evenodd" d="M11.41 9H.59C0 9 0 8.59 0 8c0-.59 0-1 .59-1H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1h.01zm0-4H.59C0 5 0 4.59 0 4c0-.59 0-1 .59-1H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1h.01zM.59 11H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1H.59C0 13 0 12.59 0 12c0-.59 0-1 .59-1z"/></svg>
|
||||
</button>
|
||||
|
||||
<div class="site-header-menu">
|
||||
<nav class="site-header-nav">
|
||||
<a href="/features" class="js-selected-navigation-item nav-item" data-ga-click="Header, click, Nav menu - item:features" data-selected-links="/features /features/code-review /features/project-management /features/integrations /features">
|
||||
Features
|
||||
</a> <a href="/business" class="js-selected-navigation-item nav-item" data-ga-click="Header, click, Nav menu - item:business" data-selected-links="/business /business/security /business/customers /business">
|
||||
Business
|
||||
</a> <a href="/explore" class="js-selected-navigation-item nav-item" data-ga-click="Header, click, Nav menu - item:explore" data-selected-links="/explore /trending /trending/developers /stars /integrations /integrations/feature/code /integrations/feature/collaborate /integrations/feature/ship showcases showcases_search showcases_landing /explore">
|
||||
Explore
|
||||
</a> <a href="/marketplace" class="js-selected-navigation-item nav-item" data-ga-click="Header, click, Nav menu - item:marketplace" data-selected-links=" /marketplace">
|
||||
Marketplace
|
||||
</a> <a href="/pricing" class="js-selected-navigation-item nav-item" data-ga-click="Header, click, Nav menu - item:pricing" data-selected-links="/pricing /pricing/developer /pricing/team /pricing/business-hosted /pricing/business-enterprise /pricing">
|
||||
Pricing
|
||||
</a> </nav>
|
||||
|
||||
<div class="site-header-actions">
|
||||
<div class="header-search scoped-search site-scoped-search js-site-search" role="search">
|
||||
<!-- '"` --><!-- </textarea></xmp> --></option></form><form accept-charset="UTF-8" action="/vt-middleware/cryptacular/search" class="js-site-search-form" data-scoped-search-url="/vt-middleware/cryptacular/search" data-unscoped-search-url="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /></div>
|
||||
<label class="form-control header-search-wrapper js-chromeless-input-container">
|
||||
<a href="/vt-middleware/cryptacular/blob/master/NOTICE" class="header-search-scope no-underline">This repository</a>
|
||||
<input type="text"
|
||||
class="form-control header-search-input js-site-search-focus js-site-search-field is-clearable"
|
||||
data-hotkey="s"
|
||||
name="q"
|
||||
value=""
|
||||
placeholder="Search"
|
||||
aria-label="Search this repository"
|
||||
data-unscoped-placeholder="Search GitHub"
|
||||
data-scoped-placeholder="Search"
|
||||
autocapitalize="off">
|
||||
<input type="hidden" class="js-site-search-type-field" name="type" >
|
||||
</label>
|
||||
</form></div>
|
||||
|
||||
|
||||
<a class="text-bold site-header-link" href="/login?return_to=%2Fvt-middleware%2Fcryptacular%2Fblob%2Fmaster%2FNOTICE" data-ga-click="(Logged out) Header, clicked Sign in, text:sign-in">Sign in</a>
|
||||
<span class="text-gray">or</span>
|
||||
<a class="text-bold site-header-link" href="/join?source=header-repo" data-ga-click="(Logged out) Header, clicked Sign up, text:sign-up">Sign up</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div id="start-of-content" class="show-on-focus"></div>
|
||||
|
||||
<div id="js-flash-container">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div role="main">
|
||||
<div itemscope itemtype="http://schema.org/SoftwareSourceCode">
|
||||
<div id="js-repo-pjax-container" data-pjax-container>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="pagehead repohead instapaper_ignore readability-menu experiment-repo-nav">
|
||||
<div class="container repohead-details-container">
|
||||
|
||||
<ul class="pagehead-actions">
|
||||
<li>
|
||||
<a href="/login?return_to=%2Fvt-middleware%2Fcryptacular"
|
||||
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
|
||||
aria-label="You must be signed in to watch a repository" rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-eye" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6C13 14 16 8 16 8s-3-6-7.94-6zM8 12c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4zm2-4c0 1.11-.89 2-2 2-1.11 0-2-.89-2-2 0-1.11.89-2 2-2 1.11 0 2 .89 2 2z"/></svg>
|
||||
Watch
|
||||
</a>
|
||||
<a class="social-count" href="/vt-middleware/cryptacular/watchers"
|
||||
aria-label="9 users are watching this repository">
|
||||
9
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="/login?return_to=%2Fvt-middleware%2Fcryptacular"
|
||||
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
|
||||
aria-label="You must be signed in to star a repository" rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-star" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14 7 11.67 11.33 14l-.93-4.74z"/></svg>
|
||||
Star
|
||||
</a>
|
||||
|
||||
<a class="social-count js-social-count" href="/vt-middleware/cryptacular/stargazers"
|
||||
aria-label="15 users starred this repository">
|
||||
15
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="/login?return_to=%2Fvt-middleware%2Fcryptacular"
|
||||
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
|
||||
aria-label="You must be signed in to fork a repository" rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-repo-forked" height="16" version="1.1" viewBox="0 0 10 16" width="10"><path fill-rule="evenodd" d="M8 1a1.993 1.993 0 0 0-1 3.72V6L5 8 3 6V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V6.5l3 3v1.78A1.993 1.993 0 0 0 5 15a1.993 1.993 0 0 0 1-3.72V9.5l3-3V4.72A1.993 1.993 0 0 0 8 1zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3 10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3-10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"/></svg>
|
||||
Fork
|
||||
</a>
|
||||
|
||||
<a href="/vt-middleware/cryptacular/network" class="social-count"
|
||||
aria-label="1 user forked this repository">
|
||||
1
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h1 class="public ">
|
||||
<svg aria-hidden="true" class="octicon octicon-repo" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z"/></svg>
|
||||
<span class="author" itemprop="author"><a href="/vt-middleware" class="url fn" rel="author">vt-middleware</a></span><!--
|
||||
--><span class="path-divider">/</span><!--
|
||||
--><strong itemprop="name"><a href="/vt-middleware/cryptacular" data-pjax="#js-repo-pjax-container">cryptacular</a></strong>
|
||||
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<nav class="reponav js-repo-nav js-sidenav-container-pjax"
|
||||
itemscope
|
||||
itemtype="http://schema.org/BreadcrumbList"
|
||||
role="navigation"
|
||||
data-pjax="#js-repo-pjax-container">
|
||||
|
||||
<span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
|
||||
<a href="/vt-middleware/cryptacular" class="js-selected-navigation-item selected reponav-item" data-hotkey="g c" data-selected-links="repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches /vt-middleware/cryptacular" itemprop="url">
|
||||
<svg aria-hidden="true" class="octicon octicon-code" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M9.5 3L8 4.5 11.5 8 8 11.5 9.5 13 14 8 9.5 3zm-5 0L0 8l4.5 5L6 11.5 2.5 8 6 4.5 4.5 3z"/></svg>
|
||||
<span itemprop="name">Code</span>
|
||||
<meta itemprop="position" content="1">
|
||||
</a> </span>
|
||||
|
||||
<span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
|
||||
<a href="/vt-middleware/cryptacular/issues" class="js-selected-navigation-item reponav-item" data-hotkey="g i" data-selected-links="repo_issues repo_labels repo_milestones /vt-middleware/cryptacular/issues" itemprop="url">
|
||||
<svg aria-hidden="true" class="octicon octicon-issue-opened" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"/></svg>
|
||||
<span itemprop="name">Issues</span>
|
||||
<span class="Counter">4</span>
|
||||
<meta itemprop="position" content="2">
|
||||
</a> </span>
|
||||
|
||||
<span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
|
||||
<a href="/vt-middleware/cryptacular/pulls" class="js-selected-navigation-item reponav-item" data-hotkey="g p" data-selected-links="repo_pulls /vt-middleware/cryptacular/pulls" itemprop="url">
|
||||
<svg aria-hidden="true" class="octicon octicon-git-pull-request" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"/></svg>
|
||||
<span itemprop="name">Pull requests</span>
|
||||
<span class="Counter">1</span>
|
||||
<meta itemprop="position" content="3">
|
||||
</a> </span>
|
||||
|
||||
<a href="/vt-middleware/cryptacular/projects" class="js-selected-navigation-item reponav-item" data-selected-links="repo_projects new_repo_project repo_project /vt-middleware/cryptacular/projects">
|
||||
<svg aria-hidden="true" class="octicon octicon-project" height="16" version="1.1" viewBox="0 0 15 16" width="15"><path fill-rule="evenodd" d="M10 12h3V2h-3v10zm-4-2h3V2H6v8zm-4 4h3V2H2v12zm-1 1h13V1H1v14zM14 0H1a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1V1a1 1 0 0 0-1-1z"/></svg>
|
||||
Projects
|
||||
<span class="Counter" >0</span>
|
||||
</a>
|
||||
|
||||
|
||||
<div class="reponav-dropdown js-menu-container">
|
||||
<button type="button" class="btn-link reponav-item reponav-dropdown js-menu-target " data-no-toggle aria-expanded="false" aria-haspopup="true">
|
||||
Insights
|
||||
<svg aria-hidden="true" class="octicon octicon-triangle-down v-align-middle text-gray" height="11" version="1.1" viewBox="0 0 12 16" width="8"><path fill-rule="evenodd" d="M0 5l6 6 6-6z"/></svg>
|
||||
</button>
|
||||
<div class="dropdown-menu-content js-menu-content">
|
||||
<div class="dropdown-menu dropdown-menu-sw">
|
||||
<a class="dropdown-item" href="/vt-middleware/cryptacular/pulse" data-skip-pjax>
|
||||
<svg aria-hidden="true" class="octicon octicon-pulse" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M11.5 8L8.8 5.4 6.6 8.5 5.5 1.6 2.38 8H0v2h3.6l.9-1.8.9 5.4L9 8.5l1.6 1.5H14V8z"/></svg>
|
||||
Pulse
|
||||
</a>
|
||||
<a class="dropdown-item" href="/vt-middleware/cryptacular/graphs" data-skip-pjax>
|
||||
<svg aria-hidden="true" class="octicon octicon-graph" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M16 14v1H0V0h1v14h15zM5 13H3V8h2v5zm4 0H7V3h2v10zm4 0h-2V6h2v7z"/></svg>
|
||||
Graphs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container new-discussion-timeline experiment-repo-nav">
|
||||
<div class="repository-content">
|
||||
|
||||
|
||||
<a href="/vt-middleware/cryptacular/blob/c26911e3cd28497ce9daa3ce682e09cb2d1d8688/NOTICE" class="d-none js-permalink-shortcut" data-hotkey="y">Permalink</a>
|
||||
|
||||
<!-- blob contrib key: blob_contributors:v21:f5054b9e46039e0ad937c69e6151b7d4 -->
|
||||
|
||||
<div class="file-navigation js-zeroclipboard-container">
|
||||
|
||||
<div class="select-menu branch-select-menu js-menu-container js-select-menu float-left">
|
||||
<button class=" btn btn-sm select-menu-button js-menu-target css-truncate" data-hotkey="w"
|
||||
|
||||
type="button" aria-label="Switch branches or tags" aria-expanded="false" aria-haspopup="true">
|
||||
<i>Branch:</i>
|
||||
<span class="js-select-button css-truncate-target">master</span>
|
||||
</button>
|
||||
|
||||
<div class="select-menu-modal-holder js-menu-content js-navigation-container" data-pjax>
|
||||
|
||||
<div class="select-menu-modal">
|
||||
<div class="select-menu-header">
|
||||
<svg aria-label="Close" class="octicon octicon-x js-menu-close" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"/></svg>
|
||||
<span class="select-menu-title">Switch branches/tags</span>
|
||||
</div>
|
||||
|
||||
<div class="select-menu-filters">
|
||||
<div class="select-menu-text-filter">
|
||||
<input type="text" aria-label="Filter branches/tags" id="context-commitish-filter-field" class="form-control js-filterable-field js-navigation-enable" placeholder="Filter branches/tags">
|
||||
</div>
|
||||
<div class="select-menu-tabs">
|
||||
<ul>
|
||||
<li class="select-menu-tab">
|
||||
<a href="#" data-tab-filter="branches" data-filter-placeholder="Filter branches/tags" class="js-select-menu-tab" role="tab">Branches</a>
|
||||
</li>
|
||||
<li class="select-menu-tab">
|
||||
<a href="#" data-tab-filter="tags" data-filter-placeholder="Find a tag…" class="js-select-menu-tab" role="tab">Tags</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="branches" role="menu">
|
||||
|
||||
<div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
|
||||
|
||||
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/blob/ISSUE-31+32/NOTICE"
|
||||
data-name="ISSUE-31+32"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
|
||||
ISSUE-31+32
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/blob/gh-pages/NOTICE"
|
||||
data-name="gh-pages"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
|
||||
gh-pages
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open selected"
|
||||
href="/vt-middleware/cryptacular/blob/master/NOTICE"
|
||||
data-name="master"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
|
||||
master
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/blob/v1.1/NOTICE"
|
||||
data-name="v1.1"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
|
||||
v1.1
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="select-menu-no-results">Nothing to show</div>
|
||||
</div>
|
||||
|
||||
<div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="tags">
|
||||
<div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
|
||||
|
||||
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/tree/v1.2.1/NOTICE"
|
||||
data-name="v1.2.1"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target" title="v1.2.1">
|
||||
v1.2.1
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/tree/v1.2.0/NOTICE"
|
||||
data-name="v1.2.0"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target" title="v1.2.0">
|
||||
v1.2.0
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/tree/v1.1.2/NOTICE"
|
||||
data-name="v1.1.2"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target" title="v1.1.2">
|
||||
v1.1.2
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/tree/v1.1.1/NOTICE"
|
||||
data-name="v1.1.1"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target" title="v1.1.1">
|
||||
v1.1.1
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/tree/v1.1.0/NOTICE"
|
||||
data-name="v1.1.0"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target" title="v1.1.0">
|
||||
v1.1.0
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/tree/v1.0/NOTICE"
|
||||
data-name="v1.0"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target" title="v1.0">
|
||||
v1.0
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/tree/v1.0-RC6/NOTICE"
|
||||
data-name="v1.0-RC6"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target" title="v1.0-RC6">
|
||||
v1.0-RC6
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/tree/v1.0-RC4/NOTICE"
|
||||
data-name="v1.0-RC4"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target" title="v1.0-RC4">
|
||||
v1.0-RC4
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/tree/v1.0-RC3/NOTICE"
|
||||
data-name="v1.0-RC3"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target" title="v1.0-RC3">
|
||||
v1.0-RC3
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/tree/v1.0-RC2/NOTICE"
|
||||
data-name="v1.0-RC2"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target" title="v1.0-RC2">
|
||||
v1.0-RC2
|
||||
</span>
|
||||
</a>
|
||||
<a class="select-menu-item js-navigation-item js-navigation-open "
|
||||
href="/vt-middleware/cryptacular/tree/v1.0-RC1/NOTICE"
|
||||
data-name="v1.0-RC1"
|
||||
data-skip-pjax="true"
|
||||
rel="nofollow">
|
||||
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"/></svg>
|
||||
<span class="select-menu-item-text css-truncate-target" title="v1.0-RC1">
|
||||
v1.0-RC1
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="select-menu-no-results">Nothing to show</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="BtnGroup float-right">
|
||||
<a href="/vt-middleware/cryptacular/find/master"
|
||||
class="js-pjax-capture-input btn btn-sm BtnGroup-item"
|
||||
data-pjax
|
||||
data-hotkey="t">
|
||||
Find file
|
||||
</a>
|
||||
<button aria-label="Copy file path to clipboard" class="js-zeroclipboard btn btn-sm BtnGroup-item tooltipped tooltipped-s" data-copied-hint="Copied!" type="button">Copy path</button>
|
||||
</div>
|
||||
<div class="breadcrumb js-zeroclipboard-target">
|
||||
<span class="repo-root js-repo-root"><span class="js-path-segment"><a href="/vt-middleware/cryptacular"><span>cryptacular</span></a></span></span><span class="separator">/</span><strong class="final-path">NOTICE</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="commit-tease">
|
||||
<span class="float-right">
|
||||
<a class="commit-tease-sha" href="/vt-middleware/cryptacular/commit/6dd6f199ac3ecc3b4c5aef9e04be3bbe265a30a1" data-pjax>
|
||||
6dd6f19
|
||||
</a>
|
||||
<relative-time datetime="2017-07-06T22:28:36Z">Jul 7, 2017</relative-time>
|
||||
</span>
|
||||
<div>
|
||||
<img alt="@dfish3r" class="avatar" height="20" src="https://avatars6.githubusercontent.com/u/1051499?v=4&s=40" width="20" />
|
||||
<a href="/dfish3r" class="user-mention" rel="contributor">dfish3r</a>
|
||||
<a href="/vt-middleware/cryptacular/commit/6dd6f199ac3ecc3b4c5aef9e04be3bbe265a30a1" class="message" data-pjax="true" title="Update year in notice.">Update year in notice.</a>
|
||||
</div>
|
||||
|
||||
<div class="commit-tease-contributors">
|
||||
<button type="button" class="btn-link muted-link contributors-toggle" data-facebox="#blob_contributors_box">
|
||||
<strong>1</strong>
|
||||
contributor
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="blob_contributors_box" style="display:none">
|
||||
<h2 class="facebox-header" data-facebox-id="facebox-header">Users who have contributed to this file</h2>
|
||||
<ul class="facebox-user-list" data-facebox-id="facebox-description">
|
||||
<li class="facebox-user-list-item">
|
||||
<img alt="@dfish3r" height="24" src="https://avatars4.githubusercontent.com/u/1051499?v=4&s=48" width="24" />
|
||||
<a href="/dfish3r">dfish3r</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="file">
|
||||
<div class="file-header">
|
||||
<div class="file-actions">
|
||||
|
||||
<div class="BtnGroup">
|
||||
<a href="/vt-middleware/cryptacular/raw/master/NOTICE" class="btn btn-sm BtnGroup-item" id="raw-url">Raw</a>
|
||||
<a href="/vt-middleware/cryptacular/blame/master/NOTICE" class="btn btn-sm js-update-url-with-hash BtnGroup-item" data-hotkey="b">Blame</a>
|
||||
<a href="/vt-middleware/cryptacular/commits/master/NOTICE" class="btn btn-sm BtnGroup-item" rel="nofollow">History</a>
|
||||
</div>
|
||||
|
||||
|
||||
<button type="button" class="btn-octicon disabled tooltipped tooltipped-nw"
|
||||
aria-label="You must be signed in to make or propose changes">
|
||||
<svg aria-hidden="true" class="octicon octicon-pencil" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"/></svg>
|
||||
</button>
|
||||
<button type="button" class="btn-octicon btn-octicon-danger disabled tooltipped tooltipped-nw"
|
||||
aria-label="You must be signed in to make or propose changes">
|
||||
<svg aria-hidden="true" class="octicon octicon-trashcan" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M11 2H9c0-.55-.45-1-1-1H5c-.55 0-1 .45-1 1H2c-.55 0-1 .45-1 1v1c0 .55.45 1 1 1v9c0 .55.45 1 1 1h7c.55 0 1-.45 1-1V5c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 12H3V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9zm1-10H2V3h9v1z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="file-info">
|
||||
7 lines (5 sloc)
|
||||
<span class="file-info-divider"></span>
|
||||
165 Bytes
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div itemprop="text" class="blob-wrapper data type-text">
|
||||
<table class="highlight tab-size js-file-line-container" data-tab-size="8">
|
||||
<tr>
|
||||
<td id="L1" class="blob-num js-line-number" data-line-number="1"></td>
|
||||
<td id="LC1" class="blob-code blob-code-inner js-file-line">Cryptacular Java Library</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="L2" class="blob-num js-line-number" data-line-number="2"></td>
|
||||
<td id="LC2" class="blob-code blob-code-inner js-file-line">Copyright (C) 2003-2017 Virginia Tech.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="L3" class="blob-num js-line-number" data-line-number="3"></td>
|
||||
<td id="LC3" class="blob-code blob-code-inner js-file-line">All rights reserved.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="L4" class="blob-num js-line-number" data-line-number="4"></td>
|
||||
<td id="LC4" class="blob-code blob-code-inner js-file-line">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="L5" class="blob-num js-line-number" data-line-number="5"></td>
|
||||
<td id="LC5" class="blob-code blob-code-inner js-file-line">This product includes software developed at</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="L6" class="blob-num js-line-number" data-line-number="6"></td>
|
||||
<td id="LC6" class="blob-code blob-code-inner js-file-line">Virginia Tech (http://www.vt.edu).</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<button type="button" data-facebox="#jump-to-line" data-facebox-class="linejump" data-hotkey="l" class="d-none">Jump to Line</button>
|
||||
<div id="jump-to-line" style="display:none">
|
||||
<!-- '"` --><!-- </textarea></xmp> --></option></form><form accept-charset="UTF-8" action="" class="js-jump-to-line-form" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /></div>
|
||||
<input class="form-control linejump-input js-jump-to-line-field" type="text" placeholder="Jump to line…" aria-label="Jump to line" autofocus>
|
||||
<button type="submit" class="btn">Go</button>
|
||||
</form> </div>
|
||||
|
||||
</div>
|
||||
<div class="modal-backdrop js-touch-events"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container site-footer-container">
|
||||
<div class="site-footer " role="contentinfo">
|
||||
<ul class="site-footer-links float-right">
|
||||
<li><a href="https://github.com/contact" data-ga-click="Footer, go to contact, text:contact">Contact GitHub</a></li>
|
||||
<li><a href="https://developer.github.com" data-ga-click="Footer, go to api, text:api">API</a></li>
|
||||
<li><a href="https://training.github.com" data-ga-click="Footer, go to training, text:training">Training</a></li>
|
||||
<li><a href="https://shop.github.com" data-ga-click="Footer, go to shop, text:shop">Shop</a></li>
|
||||
<li><a href="https://github.com/blog" data-ga-click="Footer, go to blog, text:blog">Blog</a></li>
|
||||
<li><a href="https://github.com/about" data-ga-click="Footer, go to about, text:about">About</a></li>
|
||||
|
||||
</ul>
|
||||
|
||||
<a href="https://github.com" aria-label="Homepage" class="site-footer-mark" title="GitHub">
|
||||
<svg aria-hidden="true" class="octicon octicon-mark-github" height="24" version="1.1" viewBox="0 0 16 16" width="24"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
|
||||
</a>
|
||||
<ul class="site-footer-links">
|
||||
<li>© 2017 <span title="0.10938s from unicorn-2925809464-f2ltq">GitHub</span>, Inc.</li>
|
||||
<li><a href="https://github.com/site/terms" data-ga-click="Footer, go to terms, text:terms">Terms</a></li>
|
||||
<li><a href="https://github.com/site/privacy" data-ga-click="Footer, go to privacy, text:privacy">Privacy</a></li>
|
||||
<li><a href="https://github.com/security" data-ga-click="Footer, go to security, text:security">Security</a></li>
|
||||
<li><a href="https://status.github.com/" data-ga-click="Footer, go to status, text:status">Status</a></li>
|
||||
<li><a href="https://help.github.com" data-ga-click="Footer, go to help, text:help">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div id="ajax-error-message" class="ajax-error-message flash flash-error">
|
||||
<svg aria-hidden="true" class="octicon octicon-alert" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"/></svg>
|
||||
<button type="button" class="flash-close js-flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
|
||||
<svg aria-hidden="true" class="octicon octicon-x" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"/></svg>
|
||||
</button>
|
||||
You can't perform that action at this time.
|
||||
</div>
|
||||
|
||||
|
||||
<script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/compat-91f98c37fc84eac24836eec2567e9912742094369a04c4eba6e3cd1fa18902d9.js"></script>
|
||||
<script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/frameworks-f84bb87b149685d1e6c6f057ee324f2cd496e677f5a359a8b5db853313bb83e6.js"></script>
|
||||
|
||||
<script async="async" crossorigin="anonymous" src="https://assets-cdn.github.com/assets/github-13fa3aa50ac8f9fa9a7d198f0cd13b0905775d39446ad076d17d8f74a998438a.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="js-stale-session-flash stale-session-flash flash flash-warn flash-banner d-none">
|
||||
<svg aria-hidden="true" class="octicon octicon-alert" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"/></svg>
|
||||
<span class="signed-in-tab-flash">You signed in with another tab or window. <a href="">Reload</a> to refresh your session.</span>
|
||||
<span class="signed-out-tab-flash">You signed out in another tab or window. <a href="">Reload</a> to refresh your session.</span>
|
||||
</div>
|
||||
<div class="facebox" id="facebox" style="display:none;">
|
||||
<div class="facebox-popup">
|
||||
<div class="facebox-content" role="dialog" aria-labelledby="facebox-header" aria-describedby="facebox-description">
|
||||
</div>
|
||||
<button type="button" class="facebox-close js-facebox-close" aria-label="Close modal">
|
||||
<svg aria-hidden="true" class="octicon octicon-x" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1 @@
|
|||
6ce200f6b23222af3d8abb6b6459e6c44f4bb0e9
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed 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.
|
|
@ -0,0 +1,558 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
=========================================================================
|
||||
|
||||
This project includes Public Suffix List copied from
|
||||
<https://publicsuffix.org/list/effective_tld_names.dat>
|
||||
licensed under the terms of the Mozilla Public License, v. 2.0
|
||||
|
||||
Full license text: <http://mozilla.org/MPL/2.0/>
|
||||
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
|
@ -0,0 +1,6 @@
|
|||
Apache HttpComponents Client
|
||||
Copyright 1999-2016 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
|
@ -0,0 +1 @@
|
|||
bd50ea83908dbf2f387a333216e66d2f0c5079bd
|
|
@ -0,0 +1 @@
|
|||
f4be009e7505f6ceddf21e7960c759f413f15056
|
|
@ -0,0 +1 @@
|
|||
288ecc17f2025ad14f768163d42808987d5ffcd6
|
|
@ -0,0 +1 @@
|
|||
0a97a849b18b3798c4af1a2ca5b10c66cef17e3a
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed 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.
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
Apache Log4j SLF4J Binding
|
||||
Copyright 1999-2017 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
cd9886f498ee2ab2d994f0c779e5553b2c450416
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed 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.
|
|
@ -0,0 +1,4 @@
|
|||
Dropwizard
|
||||
Copyright 2010-2013 Coda Hale and Yammer, Inc., 2014-2016 Dropwizard Team
|
||||
|
||||
This product includes software developed by Coda Hale and Yammer, Inc.
|
|
@ -0,0 +1 @@
|
|||
6fac68342891abec3c22d53e14c706ba3e58918b
|
|
@ -0,0 +1 @@
|
|||
5da0ff5d28546b3af8cc1487b4717fdeb675b8c4
|
|
@ -0,0 +1 @@
|
|||
38b21389971105f32099d04c6f63b4af505364ca
|
|
@ -0,0 +1 @@
|
|||
e4c72301b98cf4967c49c450de7da2dbc1f6b8d0
|
|
@ -0,0 +1 @@
|
|||
25c28fb4ab027fcaacaa268902cffc4451ac840c
|
|
@ -0,0 +1 @@
|
|||
c9611395e073206e59816b0b5ce5166450e8101e
|
|
@ -0,0 +1 @@
|
|||
391ac88f96a9f8f522d693c168d4c65fad20535d
|
|
@ -0,0 +1 @@
|
|||
89477899f0836040e9a584b451895a61d923bf96
|
|
@ -0,0 +1 @@
|
|||
48cf37a5080ee406aef21a49045f5e1d15ea46e6
|
|
@ -0,0 +1 @@
|
|||
4e900056cd80c1f0bd72497c26a48664089e04a8
|
|
@ -0,0 +1 @@
|
|||
ea912fe660d11ad443775974e3208f0563edcebd
|
|
@ -0,0 +1 @@
|
|||
7492688b067dca0568554ec4c7abf9f0b5e1f682
|
|
@ -0,0 +1 @@
|
|||
1244ecd4e8eccf74eb178906b0e9cac8a62bcbf7
|
|
@ -0,0 +1 @@
|
|||
e824f1e3ec14080412a4ab4b0807a13933d9be80
|
|
@ -0,0 +1 @@
|
|||
569ae8fc7c84817c5324e9f9b7958adf700a94c1
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed 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.
|
|
@ -0,0 +1 @@
|
|||
8619e95939167fb37245b5670135e4feb0ec7d50
|
|
@ -0,0 +1,21 @@
|
|||
Copyright (c) 2004-2017 QOS.ch
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1 @@
|
|||
f5995bd4cd75816568c3b26d2552d957316ba8dc
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed 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.
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
Apache XML Security for Java
|
||||
Copyright 2000-2016 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
# or more contributor license agreements. Licensed under the Elastic License;
|
||||
# you may not use this file except in compliance with the Elastic License.
|
||||
|
||||
source "`dirname "$0"`"/../elasticsearch-env
|
||||
|
||||
source "`dirname "$0"`"/x-pack-security-env
|
||||
|
||||
exec \
|
||||
"$JAVA" \
|
||||
$ES_JAVA_OPTS \
|
||||
-Des.path.home="$ES_HOME" \
|
||||
-Des.path.conf="$ES_PATH_CONF" \
|
||||
-cp "$ES_CLASSPATH" \
|
||||
org.elasticsearch.xpack.security.authc.saml.SamlMetadataCommand \
|
||||
"$@"
|
|
@ -0,0 +1,23 @@
|
|||
@echo off
|
||||
|
||||
rem Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
rem or more contributor license agreements. Licensed under the Elastic License;
|
||||
rem you may not use this file except in compliance with the Elastic License.
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
setlocal enableextensions
|
||||
|
||||
call "%~dp0..\elasticsearch-env.bat" || exit /b 1
|
||||
|
||||
call "%~dp0x-pack-security-env.bat" || exit /b 1
|
||||
|
||||
%JAVA% ^
|
||||
%ES_JAVA_OPTS% ^
|
||||
-Des.path.home="%ES_HOME%" ^
|
||||
-Des.path.conf="%ES_PATH_CONF%" ^
|
||||
-cp "%ES_CLASSPATH%" ^
|
||||
org.elasticsearch.xpack.security.authc.saml.SamlMetadataCommand ^
|
||||
%*
|
||||
|
||||
endlocal
|
||||
endlocal
|
|
@ -104,6 +104,14 @@ import org.elasticsearch.xpack.security.action.rolemapping.PutRoleMappingAction;
|
|||
import org.elasticsearch.xpack.security.action.rolemapping.TransportDeleteRoleMappingAction;
|
||||
import org.elasticsearch.xpack.security.action.rolemapping.TransportGetRoleMappingsAction;
|
||||
import org.elasticsearch.xpack.security.action.rolemapping.TransportPutRoleMappingAction;
|
||||
import org.elasticsearch.xpack.security.action.saml.SamlAuthenticateAction;
|
||||
import org.elasticsearch.xpack.security.action.saml.SamlInvalidateSessionAction;
|
||||
import org.elasticsearch.xpack.security.action.saml.SamlPrepareAuthenticationAction;
|
||||
import org.elasticsearch.xpack.security.action.saml.SamlLogoutAction;
|
||||
import org.elasticsearch.xpack.security.action.saml.TransportSamlAuthenticateAction;
|
||||
import org.elasticsearch.xpack.security.action.saml.TransportSamlInvalidateSessionAction;
|
||||
import org.elasticsearch.xpack.security.action.saml.TransportSamlPrepareAuthenticationAction;
|
||||
import org.elasticsearch.xpack.security.action.saml.TransportSamlLogoutAction;
|
||||
import org.elasticsearch.xpack.security.action.token.CreateTokenAction;
|
||||
import org.elasticsearch.xpack.security.action.token.InvalidateTokenAction;
|
||||
import org.elasticsearch.xpack.security.action.token.RefreshTokenAction;
|
||||
|
@ -171,6 +179,10 @@ import org.elasticsearch.xpack.security.rest.action.role.RestPutRoleAction;
|
|||
import org.elasticsearch.xpack.security.rest.action.rolemapping.RestDeleteRoleMappingAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.rolemapping.RestGetRoleMappingsAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.rolemapping.RestPutRoleMappingAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlAuthenticateAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlInvalidateSessionAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlPrepareAuthenticationAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlLogoutAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.user.RestChangePasswordAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.user.RestDeleteUserAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.user.RestGetUsersAction;
|
||||
|
@ -658,7 +670,11 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
|||
new ActionHandler<>(CreateTokenAction.INSTANCE, TransportCreateTokenAction.class),
|
||||
new ActionHandler<>(InvalidateTokenAction.INSTANCE, TransportInvalidateTokenAction.class),
|
||||
new ActionHandler<>(GetCertificateInfoAction.INSTANCE, TransportGetCertificateInfoAction.class),
|
||||
new ActionHandler<>(RefreshTokenAction.INSTANCE, TransportRefreshTokenAction.class)
|
||||
new ActionHandler<>(RefreshTokenAction.INSTANCE, TransportRefreshTokenAction.class),
|
||||
new ActionHandler<>(SamlPrepareAuthenticationAction.INSTANCE, TransportSamlPrepareAuthenticationAction.class),
|
||||
new ActionHandler<>(SamlAuthenticateAction.INSTANCE, TransportSamlAuthenticateAction.class),
|
||||
new ActionHandler<>(SamlLogoutAction.INSTANCE, TransportSamlLogoutAction.class),
|
||||
new ActionHandler<>(SamlInvalidateSessionAction.INSTANCE, TransportSamlInvalidateSessionAction.class)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -699,7 +715,11 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
|||
new RestDeleteRoleMappingAction(settings, restController, getLicenseState()),
|
||||
new RestGetTokenAction(settings, restController, getLicenseState()),
|
||||
new RestInvalidateTokenAction(settings, restController, getLicenseState()),
|
||||
new RestGetCertificateInfoAction(settings, restController)
|
||||
new RestGetCertificateInfoAction(settings, restController),
|
||||
new RestSamlPrepareAuthenticationAction(settings, restController, getLicenseState()),
|
||||
new RestSamlAuthenticateAction(settings, restController, getLicenseState()),
|
||||
new RestSamlLogoutAction(settings, restController, getLicenseState()),
|
||||
new RestSamlInvalidateSessionAction(settings, restController, getLicenseState())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.ActionFilters;
|
||||
import org.elasticsearch.action.support.HandledTransportAction;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.security.authc.AuthenticationService;
|
||||
import org.elasticsearch.xpack.security.authc.TokenService;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlRealm;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlToken;
|
||||
|
||||
/**
|
||||
* Transport action responsible for taking saml content and turning it into a token.
|
||||
*/
|
||||
public final class TransportSamlAuthenticateAction extends HandledTransportAction<SamlAuthenticateRequest, SamlAuthenticateResponse> {
|
||||
|
||||
private final AuthenticationService authenticationService;
|
||||
private final TokenService tokenService;
|
||||
|
||||
@Inject
|
||||
public TransportSamlAuthenticateAction(Settings settings, ThreadPool threadPool, TransportService transportService,
|
||||
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
|
||||
AuthenticationService authenticationService, TokenService tokenService) {
|
||||
super(settings, SamlAuthenticateAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
|
||||
SamlAuthenticateRequest::new);
|
||||
this.authenticationService = authenticationService;
|
||||
this.tokenService = tokenService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute(SamlAuthenticateRequest request,
|
||||
ActionListener<SamlAuthenticateResponse> listener) {
|
||||
final SamlToken saml = new SamlToken(request.getSaml(), request.getValidRequestIds());
|
||||
logger.trace("Attempting to authenticate SamlToken [{}]", saml);
|
||||
final ThreadContext threadContext = threadPool.getThreadContext();
|
||||
Authentication originatingAuthentication = Authentication.getAuthentication(threadContext);
|
||||
try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
|
||||
authenticationService.authenticate(SamlAuthenticateAction.NAME, request, saml, ActionListener.wrap(authentication -> {
|
||||
final Map<String, Object> tokenMeta = threadContext.getTransient(SamlRealm.CONTEXT_TOKEN_DATA);
|
||||
tokenService.createUserToken(authentication, originatingAuthentication,
|
||||
ActionListener.wrap(tuple -> {
|
||||
final String tokenString = tokenService.getUserTokenString(tuple.v1());
|
||||
final TimeValue expiresIn = tokenService.getExpirationDelay();
|
||||
listener.onResponse(
|
||||
new SamlAuthenticateResponse(authentication.getUser().principal(), tokenString, tuple.v2(), expiresIn));
|
||||
}, listener::onFailure), tokenMeta);
|
||||
}, e -> {
|
||||
logger.debug(() -> new ParameterizedMessage("SamlToken [{}] could not be authenticated", saml), e);
|
||||
listener.onFailure(e);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.ActionFilters;
|
||||
import org.elasticsearch.action.support.GroupedActionListener;
|
||||
import org.elasticsearch.action.support.HandledTransportAction;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.security.authc.Realms;
|
||||
import org.elasticsearch.xpack.security.authc.TokenService;
|
||||
import org.elasticsearch.xpack.security.authc.UserToken;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlLogoutRequestHandler;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlRealm;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlRedirect;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlUtils;
|
||||
import org.opensaml.saml.saml2.core.LogoutResponse;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.elasticsearch.xpack.security.authc.saml.SamlRealm.findSamlRealms;
|
||||
|
||||
/**
|
||||
* Transport action responsible for taking a SAML {@code LogoutRequest} and invalidating any associated Security Tokens
|
||||
*/
|
||||
public final class TransportSamlInvalidateSessionAction
|
||||
extends HandledTransportAction<SamlInvalidateSessionRequest, SamlInvalidateSessionResponse> {
|
||||
|
||||
private final TokenService tokenService;
|
||||
private final Realms realms;
|
||||
|
||||
@Inject
|
||||
public TransportSamlInvalidateSessionAction(Settings settings, ThreadPool threadPool, TransportService transportService,
|
||||
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
|
||||
TokenService tokenService, Realms realms) {
|
||||
super(settings, SamlInvalidateSessionAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
|
||||
SamlInvalidateSessionRequest::new);
|
||||
this.tokenService = tokenService;
|
||||
this.realms = realms;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute(SamlInvalidateSessionRequest request,
|
||||
ActionListener<SamlInvalidateSessionResponse> listener) {
|
||||
List<SamlRealm> realms = findSamlRealms(this.realms, request.getRealmName(), request.getAssertionConsumerServiceURL());
|
||||
if (realms.isEmpty()) {
|
||||
listener.onFailure(SamlUtils.samlException("Cannot find any matching realm for [{}]", request));
|
||||
} else if (realms.size() > 1) {
|
||||
listener.onFailure(SamlUtils.samlException("Found multiple matching realms [{}] for [{}]", realms, request));
|
||||
} else {
|
||||
invalidateSession(realms.get(0), request, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateSession(SamlRealm realm, SamlInvalidateSessionRequest request,
|
||||
ActionListener<SamlInvalidateSessionResponse> listener) {
|
||||
try {
|
||||
final SamlLogoutRequestHandler.Result result = realm.getLogoutHandler().parseFromQueryString(request.getQueryString());
|
||||
findAndInvalidateTokens(realm, result, ActionListener.wrap(count -> listener.onResponse(
|
||||
new SamlInvalidateSessionResponse(realm.name(), count, buildLogoutResponseUrl(realm, result))
|
||||
), listener::onFailure));
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
logger.info("Failed to invalidate SAML session", e);
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildLogoutResponseUrl(SamlRealm realm, SamlLogoutRequestHandler.Result result) {
|
||||
final LogoutResponse response = realm.buildLogoutResponse(result.getRequestId());
|
||||
return new SamlRedirect(response, realm.getSigningConfiguration()).getRedirectUrl(result.getRelayState());
|
||||
}
|
||||
|
||||
private void findAndInvalidateTokens(SamlRealm realm, SamlLogoutRequestHandler.Result result, ActionListener<Integer> listener) {
|
||||
final Map<String, Object> tokenMetadata = realm.createTokenMetadata(result.getNameId(), result.getSession());
|
||||
if (Strings.hasText((String) tokenMetadata.get(SamlRealm.TOKEN_METADATA_NAMEID_VALUE)) == false) {
|
||||
// If we don't have a valid name-id to match against, don't do anything
|
||||
logger.debug("Logout request [{}] has no NameID value, so cannot invalidate any sessions", result);
|
||||
listener.onResponse(0);
|
||||
return;
|
||||
}
|
||||
|
||||
tokenService.findActiveTokensForRealm(realm.name(), ActionListener.wrap(tokens -> {
|
||||
List<Tuple<UserToken, String>> sessionTokens = filterTokens(tokens, tokenMetadata);
|
||||
logger.debug("Found [{}] token pairs to invalidate for SAML metadata [{}]", sessionTokens.size(), tokenMetadata);
|
||||
if (sessionTokens.isEmpty()) {
|
||||
listener.onResponse(0);
|
||||
} else {
|
||||
GroupedActionListener<Boolean> groupedListener = new GroupedActionListener<>(
|
||||
ActionListener.wrap(collection -> listener.onResponse(collection.size()), listener::onFailure),
|
||||
sessionTokens.size(), Collections.emptyList()
|
||||
);
|
||||
sessionTokens.forEach(tuple -> invalidateTokenPair(tuple, groupedListener));
|
||||
}
|
||||
}, e -> listener.onFailure(e)
|
||||
));
|
||||
}
|
||||
|
||||
private void invalidateTokenPair(Tuple<UserToken, String> tokenPair, ActionListener<Boolean> listener) {
|
||||
// Invalidate the refresh token first, so the client doesn't trigger a refresh once the access token is invalidated
|
||||
tokenService.invalidateRefreshToken(tokenPair.v2(), ActionListener.wrap(ignore -> tokenService.invalidateAccessToken(
|
||||
tokenPair.v1(),
|
||||
ActionListener.wrap(listener::onResponse, e -> {
|
||||
logger.info("Failed to invalidate SAML access_token [{}] - {}", tokenPair.v1().getId(), e.toString());
|
||||
listener.onFailure(e);
|
||||
})), listener::onFailure));
|
||||
}
|
||||
|
||||
private List<Tuple<UserToken, String>> filterTokens(List<Tuple<UserToken, String>> tokens, Map<String, Object> requiredMetadata) {
|
||||
return tokens.stream()
|
||||
.filter(tup -> {
|
||||
Map<String, Object> actualMetadata = tup.v1().getMetadata();
|
||||
return requiredMetadata.entrySet().stream().allMatch(e -> Objects.equals(actualMetadata.get(e.getKey()), e.getValue()));
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.ActionFilters;
|
||||
import org.elasticsearch.action.support.HandledTransportAction;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.security.authc.Realm;
|
||||
import org.elasticsearch.xpack.security.authc.Realms;
|
||||
import org.elasticsearch.xpack.security.authc.TokenService;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlNameId;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlRealm;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlRedirect;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlUtils;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
import org.opensaml.saml.saml2.core.LogoutRequest;
|
||||
|
||||
/**
|
||||
* Transport action responsible for generating a SAML {@code <LogoutRequest>} as a redirect binding URL.
|
||||
*/
|
||||
public final class TransportSamlLogoutAction
|
||||
extends HandledTransportAction<SamlLogoutRequest, SamlLogoutResponse> {
|
||||
|
||||
private final Realms realms;
|
||||
private final TokenService tokenService;
|
||||
|
||||
@Inject
|
||||
public TransportSamlLogoutAction(Settings settings, ThreadPool threadPool, TransportService transportService,
|
||||
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
|
||||
Realms realms, TokenService tokenService) {
|
||||
super(settings, SamlLogoutAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
|
||||
SamlLogoutRequest::new);
|
||||
this.realms = realms;
|
||||
this.tokenService = tokenService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute(SamlLogoutRequest request,
|
||||
ActionListener<SamlLogoutResponse> listener) {
|
||||
invalidateRefreshToken(request.getRefreshToken(), ActionListener.wrap(ignore -> {
|
||||
try {
|
||||
final String token = request.getToken();
|
||||
tokenService.getAuthenticationAndMetaData(token, ActionListener.wrap(
|
||||
tuple -> {
|
||||
Authentication authentication = tuple.v1();
|
||||
final Map<String, Object> tokenMetadata = tuple.v2();
|
||||
SamlLogoutResponse response = buildResponse(authentication, tokenMetadata);
|
||||
tokenService.invalidateAccessToken(token, ActionListener.wrap(
|
||||
created -> {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("SAML Logout User [{}], Token [{}...{}]",
|
||||
authentication.getUser().principal(),
|
||||
token.substring(0, 8),
|
||||
token.substring(token.length() - 8)
|
||||
);
|
||||
}
|
||||
listener.onResponse(response);
|
||||
},
|
||||
listener::onFailure
|
||||
));
|
||||
}, listener::onFailure
|
||||
));
|
||||
} catch (IOException | ElasticsearchException e) {
|
||||
logger.debug("Internal exception during SAML logout", e);
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}, listener::onFailure));
|
||||
}
|
||||
|
||||
private void invalidateRefreshToken(String refreshToken, ActionListener<Boolean> listener) {
|
||||
if (refreshToken == null) {
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
tokenService.invalidateRefreshToken(refreshToken, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private SamlLogoutResponse buildResponse(Authentication authentication, Map<String, Object> tokenMetadata) {
|
||||
if (authentication == null) {
|
||||
throw SamlUtils.samlException("No active authentication");
|
||||
}
|
||||
final User user = authentication.getUser();
|
||||
if (user == null) {
|
||||
throw SamlUtils.samlException("No active user");
|
||||
}
|
||||
|
||||
final SamlRealm realm = findRealm(authentication);
|
||||
final String tokenRealm = getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_REALM);
|
||||
if (realm.name().equals(tokenRealm) == false) {
|
||||
throw SamlUtils.samlException("Authenticating realm [{}] does not match token realm [{}]", realm, tokenRealm);
|
||||
}
|
||||
|
||||
final SamlNameId nameId = new SamlNameId(
|
||||
getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_NAMEID_FORMAT),
|
||||
getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_NAMEID_VALUE),
|
||||
getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_NAMEID_QUALIFIER),
|
||||
getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_NAMEID_SP_QUALIFIER),
|
||||
getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_NAMEID_SP_PROVIDED_ID)
|
||||
);
|
||||
final String session = getMetadataString(tokenMetadata, SamlRealm.TOKEN_METADATA_SESSION);
|
||||
final LogoutRequest logout = realm.buildLogoutRequest(nameId.asXml(), session);
|
||||
if (logout == null) {
|
||||
return new SamlLogoutResponse(null);
|
||||
}
|
||||
final String uri = new SamlRedirect(logout, realm.getSigningConfiguration()).getRedirectUrl();
|
||||
return new SamlLogoutResponse(uri);
|
||||
}
|
||||
|
||||
private String getMetadataString(Map<String, Object> metadata, String key) {
|
||||
final Object value = metadata.get(key);
|
||||
if (value == null) {
|
||||
if (metadata.containsKey(key)) {
|
||||
return null;
|
||||
}
|
||||
throw SamlUtils.samlException("Access token does not have SAML metadata [{}]", key);
|
||||
}
|
||||
if (value instanceof String) {
|
||||
return (String) value;
|
||||
} else {
|
||||
throw SamlUtils.samlException("In access token, SAML metadata [{}] is [{}] rather than String", key, value.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private SamlRealm findRealm(Authentication authentication) {
|
||||
final Authentication.RealmRef ref = authentication.getAuthenticatedBy();
|
||||
if (ref == null || Strings.isNullOrEmpty(ref.getName())) {
|
||||
throw SamlUtils.samlException("Authentication {} has no authenticating realm", authentication);
|
||||
}
|
||||
final Realm realm = realms.realm(ref.getName());
|
||||
if (realm == null) {
|
||||
throw SamlUtils.samlException("Authenticating realm {} does not exist", ref.getName());
|
||||
}
|
||||
if (realm instanceof SamlRealm) {
|
||||
return (SamlRealm) realm;
|
||||
} else {
|
||||
throw SamlUtils.samlException("Authenticating realm {} is not a SAML realm", realm);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.saml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.ActionFilters;
|
||||
import org.elasticsearch.action.support.HandledTransportAction;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
import org.elasticsearch.xpack.security.authc.Realms;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlRealm;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlRedirect;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlUtils;
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
|
||||
import static org.elasticsearch.xpack.security.authc.saml.SamlRealm.findSamlRealms;
|
||||
|
||||
/**
|
||||
* Transport action responsible for generating a SAML {@code <AuthnRequest>} as a redirect binding URL.
|
||||
*/
|
||||
public final class TransportSamlPrepareAuthenticationAction
|
||||
extends HandledTransportAction<SamlPrepareAuthenticationRequest, SamlPrepareAuthenticationResponse> {
|
||||
|
||||
private final Realms realms;
|
||||
|
||||
@Inject
|
||||
public TransportSamlPrepareAuthenticationAction(Settings settings, ThreadPool threadPool, TransportService transportService,
|
||||
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
|
||||
Realms realms) {
|
||||
super(settings, SamlPrepareAuthenticationAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
|
||||
SamlPrepareAuthenticationRequest::new);
|
||||
this.realms = realms;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute(SamlPrepareAuthenticationRequest request,
|
||||
ActionListener<SamlPrepareAuthenticationResponse> listener) {
|
||||
List<SamlRealm> realms = findSamlRealms(this.realms, request.getRealmName(), request.getAssertionConsumerServiceURL() );
|
||||
if (realms.isEmpty()) {
|
||||
listener.onFailure(SamlUtils.samlException("Cannot find any matching realm for [{}]", request));
|
||||
} else if (realms.size() > 1) {
|
||||
listener.onFailure(SamlUtils.samlException("Found multiple matching realms [{}] for [{}]", realms, request));
|
||||
} else {
|
||||
prepareAuthentication(realms.get(0), listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareAuthentication(SamlRealm realm, ActionListener<SamlPrepareAuthenticationResponse> listener) {
|
||||
final AuthnRequest authnRequest = realm.buildAuthenticationRequest();
|
||||
try {
|
||||
String redirectUrl = new SamlRedirect(authnRequest, realm.getSigningConfiguration()).getRedirectUrl();
|
||||
listener.onResponse(new SamlPrepareAuthenticationResponse(
|
||||
realm.name(),
|
||||
authnRequest.getID(),
|
||||
redirectUrl
|
||||
));
|
||||
} catch (ElasticsearchException e) {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,8 @@ import org.elasticsearch.transport.TransportService;
|
|||
import org.elasticsearch.xpack.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.security.authc.AuthenticationService;
|
||||
import org.elasticsearch.xpack.security.authc.TokenService;
|
||||
import org.elasticsearch.xpack.security.authc.UserToken;
|
||||
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
|
@ -46,8 +48,8 @@ public final class TransportCreateTokenAction extends HandledTransportAction<Cre
|
|||
protected void doExecute(CreateTokenRequest request, ActionListener<CreateTokenResponse> listener) {
|
||||
Authentication originatingAuthentication = Authentication.getAuthentication(threadPool.getThreadContext());
|
||||
try (ThreadContext.StoredContext ignore = threadPool.getThreadContext().stashContext()) {
|
||||
authenticationService.authenticate(CreateTokenAction.NAME, request,
|
||||
request.getUsername(), request.getPassword(),
|
||||
final UsernamePasswordToken authToken = new UsernamePasswordToken(request.getUsername(), request.getPassword());
|
||||
authenticationService.authenticate(CreateTokenAction.NAME, request, authToken,
|
||||
ActionListener.wrap(authentication -> {
|
||||
request.getPassword().close();
|
||||
tokenService.createUserToken(authentication, originatingAuthentication, ActionListener.wrap(tuple -> {
|
||||
|
|
|
@ -18,7 +18,6 @@ import org.elasticsearch.action.ActionListener;
|
|||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
|
@ -100,14 +99,13 @@ public class AuthenticationService extends AbstractComponent {
|
|||
* Authenticates the username and password that are provided as parameters. This will not look
|
||||
* at the values in the ThreadContext for Authentication.
|
||||
*
|
||||
* @param action The action of the message
|
||||
* @param message The message that resulted in this authenticate call
|
||||
* @param username The username to be used for authentication
|
||||
* @param password The password to be used for authentication
|
||||
* @param action The action of the message
|
||||
* @param message The message that resulted in this authenticate call
|
||||
* @param token The token (credentials) to be authenticated
|
||||
*/
|
||||
public void authenticate(String action, TransportMessage message, String username,
|
||||
SecureString password, ActionListener<Authentication> listener) {
|
||||
new Authenticator(action, message, null, listener, username, password).authenticateAsync();
|
||||
public void authenticate(String action, TransportMessage message,
|
||||
AuthenticationToken token, ActionListener<Authentication> listener) {
|
||||
new Authenticator(action, message, null, listener).authenticateToken(token);
|
||||
}
|
||||
|
||||
// pkg private method for testing
|
||||
|
@ -142,13 +140,6 @@ public class AuthenticationService extends AbstractComponent {
|
|||
this(new AuditableTransportRequest(auditTrail, failureHandler, threadContext, action, message), fallbackUser, listener);
|
||||
}
|
||||
|
||||
Authenticator(String action, TransportMessage message, User fallbackUser,
|
||||
ActionListener<Authentication> listener, String username,
|
||||
SecureString password) {
|
||||
this(new AuditableTransportRequest(auditTrail, failureHandler, threadContext, action, message), fallbackUser, listener);
|
||||
this.authenticationToken = new UsernamePasswordToken(username, password);
|
||||
}
|
||||
|
||||
private Authenticator(AuditableRequest auditableRequest, User fallbackUser, ActionListener<Authentication> listener) {
|
||||
this.request = auditableRequest;
|
||||
this.fallbackUser = fallbackUser;
|
||||
|
@ -240,6 +231,7 @@ public class AuthenticationService extends AbstractComponent {
|
|||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("An exception occurred while attempting to find authentication credentials", e);
|
||||
action = () -> listener.onFailure(request.exceptionProcessingRequest(e, null));
|
||||
}
|
||||
|
||||
|
@ -441,6 +433,10 @@ public class AuthenticationService extends AbstractComponent {
|
|||
// when an exception bubbles up even after successful authentication
|
||||
action.run();
|
||||
}
|
||||
|
||||
private void authenticateToken(AuthenticationToken token) {
|
||||
this.consumeToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class AuditableRequest {
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.util.Set;
|
|||
|
||||
import org.elasticsearch.bootstrap.BootstrapCheck;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.set.Sets;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
|
@ -30,36 +31,53 @@ import org.elasticsearch.xpack.security.authc.ldap.LdapRealm;
|
|||
import org.elasticsearch.xpack.security.authc.ldap.LdapRealmSettings;
|
||||
import org.elasticsearch.xpack.security.authc.pki.PkiRealm;
|
||||
import org.elasticsearch.xpack.security.authc.pki.PkiRealmSettings;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlRealm;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlRealmSettings;
|
||||
import org.elasticsearch.xpack.ssl.SSLService;
|
||||
|
||||
import org.elasticsearch.xpack.security.authc.support.RoleMappingFileBootstrapCheck;
|
||||
import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore;
|
||||
import org.elasticsearch.xpack.ssl.SSLService;
|
||||
|
||||
/**
|
||||
* Provides a single entry point into dealing with all standard XPack security {@link Realm realms}.
|
||||
* This class does not handle extensions.
|
||||
* @see Realms for the component that manages configured realms (including custom extension realms)
|
||||
*/
|
||||
public class InternalRealms {
|
||||
public final class InternalRealms {
|
||||
|
||||
/**
|
||||
* The list of all <em>internal</em> realm types, excluding {@link ReservedRealm#TYPE}.
|
||||
*/
|
||||
private static final Set<String> TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
NativeRealmSettings.TYPE, FileRealmSettings.TYPE, LdapRealmSettings.AD_TYPE, LdapRealmSettings.LDAP_TYPE, PkiRealmSettings.TYPE
|
||||
private static final Set<String> XPACK_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
NativeRealmSettings.TYPE, FileRealmSettings.TYPE, LdapRealmSettings.AD_TYPE, LdapRealmSettings.LDAP_TYPE, PkiRealmSettings.TYPE,
|
||||
SamlRealmSettings.TYPE
|
||||
)));
|
||||
|
||||
/**
|
||||
* Determines whether <code>type</code> is an internal realm-type, optionally considering
|
||||
* the {@link ReservedRealm}.
|
||||
* The list of all standard realm types, which are those provided by x-pack and do not have extensive
|
||||
* interaction with third party sources
|
||||
*/
|
||||
public static boolean isInternalRealm(String type, boolean includeReservedRealm) {
|
||||
if (TYPES.contains(type)) {
|
||||
private static final Set<String> STANDARD_TYPES =
|
||||
Collections.unmodifiableSet(Sets.difference(XPACK_TYPES, Collections.singleton(SamlRealmSettings.TYPE)));
|
||||
|
||||
/**
|
||||
* Determines whether <code>type</code> is an internal realm-type that is provided by x-pack,
|
||||
* including the {@link ReservedRealm}
|
||||
*/
|
||||
static boolean isXPackRealm(String type) {
|
||||
if (XPACK_TYPES.contains(type)) {
|
||||
return true;
|
||||
}
|
||||
if (includeReservedRealm && ReservedRealm.TYPE.equals(type)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return ReservedRealm.TYPE.equals(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether <code>type</code> is an internal realm-type that is provided by x-pack,
|
||||
* excluding the {@link ReservedRealm} and realms that have extensive interaction with
|
||||
* third party sources
|
||||
*/
|
||||
static boolean isStandardRealm(String type) {
|
||||
return STANDARD_TYPES.contains(type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -84,6 +102,7 @@ public class InternalRealms {
|
|||
map.put(LdapRealmSettings.LDAP_TYPE, config -> new LdapRealm(LdapRealmSettings.LDAP_TYPE, config,
|
||||
sslService, resourceWatcherService, nativeRoleMappingStore, threadPool));
|
||||
map.put(PkiRealmSettings.TYPE, config -> new PkiRealm(config, resourceWatcherService, nativeRoleMappingStore));
|
||||
map.put(SamlRealmSettings.TYPE, config -> SamlRealm.create(config, sslService, resourceWatcherService, nativeRoleMappingStore));
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
|
@ -39,11 +41,12 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
|
|||
private final ThreadContext threadContext;
|
||||
private final ReservedRealm reservedRealm;
|
||||
|
||||
protected List<Realm> realms = Collections.emptyList();
|
||||
// a list of realms that are considered default in that they are provided by x-pack and not a third party
|
||||
List<Realm> internalRealmsOnly = Collections.emptyList();
|
||||
protected List<Realm> realms;
|
||||
// a list of realms that are considered standard in that they are provided by x-pack and
|
||||
// interact with a 3rd party source on a limited basis
|
||||
List<Realm> standardRealmsOnly;
|
||||
// a list of realms that are considered native, that is they only interact with x-pack and no 3rd party auth sources
|
||||
List<Realm> nativeRealmsOnly = Collections.emptyList();
|
||||
List<Realm> nativeRealmsOnly;
|
||||
|
||||
public Realms(Settings settings, Environment env, Map<String, Realm.Factory> factories, XPackLicenseState licenseState,
|
||||
ThreadContext threadContext, ReservedRealm reservedRealm) throws Exception {
|
||||
|
@ -57,12 +60,12 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
|
|||
this.realms = initRealms();
|
||||
// pre-computing a list of internal only realms allows us to have much cheaper iteration than a custom iterator
|
||||
// and is also simpler in terms of logic. These lists are small, so the duplication should not be a real issue here
|
||||
List<Realm> internalRealms = new ArrayList<>();
|
||||
List<Realm> standardRealms = new ArrayList<>();
|
||||
List<Realm> nativeRealms = new ArrayList<>();
|
||||
for (Realm realm : realms) {
|
||||
// don't add the reserved realm here otherwise we end up with only this realm...
|
||||
if (InternalRealms.isInternalRealm(realm.type(), false)) {
|
||||
internalRealms.add(realm);
|
||||
if (InternalRealms.isStandardRealm(realm.type())) {
|
||||
standardRealms.add(realm);
|
||||
}
|
||||
|
||||
if (FileRealmSettings.TYPE.equals(realm.type()) || NativeRealmSettings.TYPE.equals(realm.type())) {
|
||||
|
@ -70,7 +73,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
|
|||
}
|
||||
}
|
||||
|
||||
for (List<Realm> realmList : Arrays.asList(internalRealms, nativeRealms)) {
|
||||
for (List<Realm> realmList : Arrays.asList(standardRealms, nativeRealms)) {
|
||||
if (realmList.isEmpty()) {
|
||||
addNativeRealms(realmList);
|
||||
}
|
||||
|
@ -80,7 +83,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
|
|||
assert realmList.get(0) == reservedRealm;
|
||||
}
|
||||
|
||||
this.internalRealmsOnly = Collections.unmodifiableList(internalRealms);
|
||||
this.standardRealmsOnly = Collections.unmodifiableList(standardRealms);
|
||||
this.nativeRealmsOnly = Collections.unmodifiableList(nativeRealms);
|
||||
}
|
||||
|
||||
|
@ -95,7 +98,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
|
|||
case ALL:
|
||||
return realms.iterator();
|
||||
case DEFAULT:
|
||||
return internalRealmsOnly.iterator();
|
||||
return standardRealmsOnly.iterator();
|
||||
case NATIVE:
|
||||
return nativeRealmsOnly.iterator();
|
||||
default:
|
||||
|
@ -103,6 +106,10 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
|
|||
}
|
||||
}
|
||||
|
||||
public Stream<Realm> stream() {
|
||||
return StreamSupport.stream(this.spliterator(), false);
|
||||
}
|
||||
|
||||
public List<Realm> asList() {
|
||||
if (licenseState.isAuthAllowed() == false) {
|
||||
return Collections.emptyList();
|
||||
|
@ -113,7 +120,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
|
|||
case ALL:
|
||||
return Collections.unmodifiableList(realms);
|
||||
case DEFAULT:
|
||||
return Collections.unmodifiableList(internalRealmsOnly);
|
||||
return Collections.unmodifiableList(standardRealmsOnly);
|
||||
case NATIVE:
|
||||
return Collections.unmodifiableList(nativeRealmsOnly);
|
||||
default:
|
||||
|
@ -265,7 +272,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
|
|||
case NATIVE:
|
||||
return FileRealmSettings.TYPE.equals(type) || NativeRealmSettings.TYPE.equals(type);
|
||||
case DEFAULT:
|
||||
return InternalRealms.isInternalRealm(type, true);
|
||||
return InternalRealms.isStandardRealm(type) || ReservedRealm.TYPE.equals(type);
|
||||
default:
|
||||
throw new IllegalStateException("unknown enabled realm type [" + enabledRealmType + "]");
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.index.engine.DocumentMissingException;
|
||||
import org.elasticsearch.index.engine.VersionConflictEngineException;
|
||||
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.xpack.XPackSettings;
|
||||
|
@ -102,6 +103,8 @@ import java.util.Optional;
|
|||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import static org.elasticsearch.action.support.TransportActions.isShardNotAvailableException;
|
||||
import static org.elasticsearch.gateway.GatewayService.STATE_NOT_RECOVERED_BLOCK;
|
||||
|
@ -138,7 +141,7 @@ public final class TokenService extends AbstractComponent {
|
|||
public static final Setting<TimeValue> TOKEN_EXPIRATION = Setting.timeSetting("xpack.security.authc.token.timeout",
|
||||
TimeValue.timeValueMinutes(20L), TimeValue.timeValueSeconds(1L), Property.NodeScope);
|
||||
public static final Setting<TimeValue> DELETE_INTERVAL = Setting.timeSetting("xpack.security.authc.token.delete.interval",
|
||||
TimeValue.timeValueMinutes(30L), Property.NodeScope);
|
||||
TimeValue.timeValueMinutes(30L), Property.NodeScope);
|
||||
public static final Setting<TimeValue> DELETE_TIMEOUT = Setting.timeSetting("xpack.security.authc.token.delete.timeout",
|
||||
TimeValue.MINUS_ONE, Property.NodeScope);
|
||||
|
||||
|
@ -161,9 +164,10 @@ public final class TokenService extends AbstractComponent {
|
|||
|
||||
/**
|
||||
* Creates a new token service
|
||||
*
|
||||
* @param settings the node settings
|
||||
* @param clock the clock that will be used for comparing timestamps
|
||||
* @param client the client to use when checking for revocations
|
||||
* @param clock the clock that will be used for comparing timestamps
|
||||
* @param client the client to use when checking for revocations
|
||||
*/
|
||||
public TokenService(Settings settings, Clock clock, Client client,
|
||||
SecurityLifecycleService lifecycleService, ClusterService clusterService) throws GeneralSecurityException {
|
||||
|
@ -178,7 +182,7 @@ public final class TokenService extends AbstractComponent {
|
|||
this.lifecycleService = lifecycleService;
|
||||
this.lastExpirationRunMs = client.threadPool().relativeTimeInMillis();
|
||||
this.deleteInterval = DELETE_INTERVAL.get(settings);
|
||||
this.enabled = XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.get(settings);
|
||||
this.enabled = isTokenServiceEnabled(settings);
|
||||
this.expiredTokenRemover = new ExpiredTokenRemover(settings, client);
|
||||
ensureEncryptionCiphersSupported();
|
||||
KeyAndCache keyAndCache = new KeyAndCache(new KeyAndTimestamp(tokenPassphrase, createdTimeStamps.incrementAndGet()),
|
||||
|
@ -189,6 +193,9 @@ public final class TokenService extends AbstractComponent {
|
|||
getTokenMetaData();
|
||||
}
|
||||
|
||||
public static Boolean isTokenServiceEnabled(Settings settings) {
|
||||
return XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.get(settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a token based on the provided authentication and metadata.
|
||||
|
@ -199,6 +206,8 @@ public final class TokenService extends AbstractComponent {
|
|||
ensureEnabled();
|
||||
if (authentication == null) {
|
||||
listener.onFailure(new IllegalArgumentException("authentication must be provided"));
|
||||
} else if (originatingClientAuth == null) {
|
||||
listener.onFailure(new IllegalArgumentException("originating client authentication must be provided"));
|
||||
} else {
|
||||
final Instant created = clock.instant();
|
||||
final Instant expiration = getExpirationTime(created);
|
||||
|
@ -226,6 +235,7 @@ public final class TokenService extends AbstractComponent {
|
|||
builder.startObject("access_token")
|
||||
.field("invalidated", false)
|
||||
.field("user_token", userToken)
|
||||
.field("realm", authentication.getAuthenticatedBy().getName())
|
||||
.endObject();
|
||||
builder.endObject();
|
||||
IndexRequest request =
|
||||
|
@ -255,19 +265,7 @@ public final class TokenService extends AbstractComponent {
|
|||
listener.onResponse(null);
|
||||
} else {
|
||||
try {
|
||||
decodeToken(token, ActionListener.wrap(userToken -> {
|
||||
if (userToken != null) {
|
||||
Instant currentTime = clock.instant();
|
||||
if (currentTime.isAfter(userToken.getExpirationTime())) {
|
||||
// token expired
|
||||
listener.onFailure(expiredTokenException());
|
||||
} else {
|
||||
checkIfTokenIsRevoked(userToken, listener);
|
||||
}
|
||||
} else {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
}, listener::onFailure));
|
||||
decodeAndValidateToken(token, listener);
|
||||
} catch (IOException e) {
|
||||
// could happen with a token that is not ours
|
||||
logger.debug("invalid token", e);
|
||||
|
@ -280,6 +278,40 @@ public final class TokenService extends AbstractComponent {
|
|||
}
|
||||
|
||||
/**
|
||||
* Reads the authentication and metadata from the given token.
|
||||
* This method does not validate whether the token is expired or not.
|
||||
*/
|
||||
public void getAuthenticationAndMetaData(String token, ActionListener<Tuple<Authentication, Map<String, Object>>> listener)
|
||||
throws IOException {
|
||||
decodeToken(token, ActionListener.wrap(
|
||||
userToken -> {
|
||||
if (userToken == null) {
|
||||
listener.onFailure(new ElasticsearchSecurityException("supplied token is not valid"));
|
||||
} else {
|
||||
listener.onResponse(new Tuple<>(userToken.getAuthentication(), userToken.getMetadata()));
|
||||
}
|
||||
},
|
||||
listener::onFailure
|
||||
));
|
||||
}
|
||||
|
||||
private void decodeAndValidateToken(String token, ActionListener<UserToken> listener) throws IOException {
|
||||
decodeToken(token, ActionListener.wrap(userToken -> {
|
||||
if (userToken != null) {
|
||||
Instant currentTime = clock.instant();
|
||||
if (currentTime.isAfter(userToken.getExpirationTime())) {
|
||||
// token expired
|
||||
listener.onFailure(expiredTokenException());
|
||||
} else {
|
||||
checkIfTokenIsRevoked(userToken, listener);
|
||||
}
|
||||
} else {
|
||||
listener.onResponse(null);
|
||||
}
|
||||
}, listener::onFailure));
|
||||
}
|
||||
|
||||
/*
|
||||
* Asynchronously decodes the string representation of a {@link UserToken}. The process for
|
||||
* this is asynchronous as we may need to compute a key, which can be computationally expensive
|
||||
* so this should not block the current thread, which is typically a network thread. A second
|
||||
|
@ -304,7 +336,49 @@ public final class TokenService extends AbstractComponent {
|
|||
getKeyAsync(decodedSalt, keyAndCache, ActionListener.wrap(decodeKey -> {
|
||||
try {
|
||||
final byte[] iv = in.readByteArray();
|
||||
decryptToken(in, getDecryptionCipher(iv, decodeKey, version, decodedSalt), version, listener);
|
||||
final Cipher cipher = getDecryptionCipher(iv, decodeKey, version, decodedSalt);
|
||||
if (version.onOrAfter(Version.V_6_2_0)) {
|
||||
// we only have the id and need to get the token from the doc!
|
||||
decryptTokenId(in, cipher, version, ActionListener.wrap(tokenId ->
|
||||
lifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> {
|
||||
final GetRequest getRequest =
|
||||
client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, TYPE,
|
||||
getTokenDocumentId(tokenId)).request();
|
||||
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, getRequest,
|
||||
ActionListener.<GetResponse>wrap(response -> {
|
||||
if (response.isExists()) {
|
||||
Map<String, Object> accessTokenSource =
|
||||
(Map<String, Object>) response.getSource().get("access_token");
|
||||
if (accessTokenSource == null) {
|
||||
listener.onFailure(new IllegalStateException("token document is missing " +
|
||||
"the access_token field"));
|
||||
} else if (accessTokenSource.containsKey("user_token") == false) {
|
||||
listener.onFailure(new IllegalStateException("token document is missing " +
|
||||
"the user_token field"));
|
||||
} else {
|
||||
Map<String, Object> userTokenSource =
|
||||
(Map<String, Object>) accessTokenSource.get("user_token");
|
||||
listener.onResponse(UserToken.fromSourceMap(userTokenSource));
|
||||
}
|
||||
} else {
|
||||
listener.onFailure(
|
||||
new IllegalStateException("token document is missing and must be present"));
|
||||
}
|
||||
}, e -> {
|
||||
// if the index or the shard is not there / available we assume that
|
||||
// the token is not valid
|
||||
if (isShardNotAvailableException(e)) {
|
||||
logger.warn("failed to get token [{}] since index is not available", tokenId);
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
logger.error(new ParameterizedMessage("failed to get token [{}]", tokenId), e);
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}), client::get);
|
||||
}), listener::onFailure));
|
||||
} else {
|
||||
decryptToken(in, cipher, version, listener);
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
// could happen with a token that is not ours
|
||||
logger.warn("invalid token", e);
|
||||
|
@ -347,6 +421,13 @@ public final class TokenService extends AbstractComponent {
|
|||
}
|
||||
}
|
||||
|
||||
private static void decryptTokenId(StreamInput in, Cipher cipher, Version version, ActionListener<String> listener) throws IOException {
|
||||
try (CipherInputStream cis = new CipherInputStream(in, cipher); StreamInput decryptedInput = new InputStreamStreamInput(cis)) {
|
||||
decryptedInput.setVersion(version);
|
||||
listener.onResponse(decryptedInput.readString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method performs the steps necessary to invalidate a token so that it may no longer be
|
||||
* used. The process of invalidation involves a step that is needed for backwards compatibility
|
||||
|
@ -377,6 +458,22 @@ public final class TokenService extends AbstractComponent {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method performs the steps necessary to invalidate a token so that it may no longer be used.
|
||||
*
|
||||
* @see #invalidateAccessToken(String, ActionListener)
|
||||
*/
|
||||
public void invalidateAccessToken(UserToken userToken, ActionListener<Boolean> listener) {
|
||||
ensureEnabled();
|
||||
if (userToken == null) {
|
||||
listener.onFailure(new IllegalArgumentException("token must be provided"));
|
||||
} else {
|
||||
maybeStartTokenRemover();
|
||||
final long expirationEpochMilli = getExpirationTime().toEpochMilli();
|
||||
indexBwcInvalidation(userToken, listener, new AtomicInteger(0), expirationEpochMilli);
|
||||
}
|
||||
}
|
||||
|
||||
public void invalidateRefreshToken(String refreshToken, ActionListener<Boolean> listener) {
|
||||
ensureEnabled();
|
||||
if (Strings.isNullOrEmpty(refreshToken)) {
|
||||
|
@ -394,13 +491,14 @@ public final class TokenService extends AbstractComponent {
|
|||
|
||||
/**
|
||||
* Performs the actual bwc invalidation of a token and then kicks off the new invalidation method
|
||||
* @param userToken the token to invalidate
|
||||
* @param listener the listener to notify upon completion
|
||||
* @param attemptCount the number of attempts to invalidate that have already been tried
|
||||
*
|
||||
* @param userToken the token to invalidate
|
||||
* @param listener the listener to notify upon completion
|
||||
* @param attemptCount the number of attempts to invalidate that have already been tried
|
||||
* @param expirationEpochMilli the expiration time as milliseconds since the epoch
|
||||
*/
|
||||
private void indexBwcInvalidation(UserToken userToken, ActionListener<Boolean> listener, AtomicInteger attemptCount,
|
||||
long expirationEpochMilli) {
|
||||
long expirationEpochMilli) {
|
||||
if (attemptCount.get() > 5) {
|
||||
listener.onFailure(invalidGrantException("failed to invalidate token"));
|
||||
} else {
|
||||
|
@ -413,34 +511,35 @@ public final class TokenService extends AbstractComponent {
|
|||
final String tokenDocId = getTokenDocumentId(userToken);
|
||||
final Version version = userToken.getVersion();
|
||||
lifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () ->
|
||||
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, indexRequest,
|
||||
ActionListener.<IndexResponse>wrap(indexResponse -> {
|
||||
ActionListener<Boolean> wrappedListener =
|
||||
ActionListener.wrap(ignore -> listener.onResponse(true), listener::onFailure);
|
||||
indexInvalidation(tokenDocId, version, wrappedListener, attemptCount, "access_token", 1L);
|
||||
}, e -> {
|
||||
Throwable cause = ExceptionsHelper.unwrapCause(e);
|
||||
if (cause instanceof VersionConflictEngineException) {
|
||||
// expected since something else could have invalidated
|
||||
ActionListener<Boolean> wrappedListener =
|
||||
ActionListener.wrap(ignore -> listener.onResponse(false), listener::onFailure);
|
||||
indexInvalidation(tokenDocId, version, wrappedListener, attemptCount, "access_token", 1L);
|
||||
} else if (isShardNotAvailableException(e)) {
|
||||
attemptCount.incrementAndGet();
|
||||
indexBwcInvalidation(userToken, listener, attemptCount, expirationEpochMilli);
|
||||
} else {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}), client::index));
|
||||
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, indexRequest,
|
||||
ActionListener.<IndexResponse>wrap(indexResponse -> {
|
||||
ActionListener<Boolean> wrappedListener =
|
||||
ActionListener.wrap(ignore -> listener.onResponse(true), listener::onFailure);
|
||||
indexInvalidation(tokenDocId, version, wrappedListener, attemptCount, "access_token", 1L);
|
||||
}, e -> {
|
||||
Throwable cause = ExceptionsHelper.unwrapCause(e);
|
||||
if (cause instanceof VersionConflictEngineException) {
|
||||
// expected since something else could have invalidated
|
||||
ActionListener<Boolean> wrappedListener =
|
||||
ActionListener.wrap(ignore -> listener.onResponse(false), listener::onFailure);
|
||||
indexInvalidation(tokenDocId, version, wrappedListener, attemptCount, "access_token", 1L);
|
||||
} else if (isShardNotAvailableException(e)) {
|
||||
attemptCount.incrementAndGet();
|
||||
indexBwcInvalidation(userToken, listener, attemptCount, expirationEpochMilli);
|
||||
} else {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}), client::index));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual invalidation of a token
|
||||
* @param tokenDocId the id of the token doc to invalidate
|
||||
* @param listener the listener to notify upon completion
|
||||
* @param attemptCount the number of attempts to invalidate that have already been tried
|
||||
* @param srcPrefix the prefix to use when constructing the doc to update
|
||||
*
|
||||
* @param tokenDocId the id of the token doc to invalidate
|
||||
* @param listener the listener to notify upon completion
|
||||
* @param attemptCount the number of attempts to invalidate that have already been tried
|
||||
* @param srcPrefix the prefix to use when constructing the doc to update
|
||||
* @param documentVersion the expected version of the document we will update
|
||||
*/
|
||||
private void indexInvalidation(String tokenDocId, Version version, ActionListener<Boolean> listener, AtomicInteger attemptCount,
|
||||
|
@ -553,29 +652,29 @@ public final class TokenService extends AbstractComponent {
|
|||
.request();
|
||||
|
||||
lifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () ->
|
||||
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
|
||||
ActionListener.<SearchResponse>wrap(searchResponse -> {
|
||||
if (searchResponse.isTimedOut()) {
|
||||
attemptCount.incrementAndGet();
|
||||
findTokenFromRefreshToken(refreshToken, listener, attemptCount);
|
||||
} else if (searchResponse.getHits().getHits().length < 1) {
|
||||
logger.info("could not find token document with refresh_token [{}]", refreshToken);
|
||||
listener.onFailure(invalidGrantException("could not refresh the requested token"));
|
||||
} else if (searchResponse.getHits().getHits().length > 1) {
|
||||
listener.onFailure(new IllegalStateException("multiple tokens share the same refresh token"));
|
||||
} else {
|
||||
listener.onResponse(new Tuple<>(searchResponse, attemptCount));
|
||||
}
|
||||
}, e -> {
|
||||
if (isShardNotAvailableException(e)) {
|
||||
logger.debug("failed to search for token document, retrying", e);
|
||||
attemptCount.incrementAndGet();
|
||||
findTokenFromRefreshToken(refreshToken, listener, attemptCount);
|
||||
} else {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}),
|
||||
client::search));
|
||||
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
|
||||
ActionListener.<SearchResponse>wrap(searchResponse -> {
|
||||
if (searchResponse.isTimedOut()) {
|
||||
attemptCount.incrementAndGet();
|
||||
findTokenFromRefreshToken(refreshToken, listener, attemptCount);
|
||||
} else if (searchResponse.getHits().getHits().length < 1) {
|
||||
logger.info("could not find token document with refresh_token [{}]", refreshToken);
|
||||
listener.onFailure(invalidGrantException("could not refresh the requested token"));
|
||||
} else if (searchResponse.getHits().getHits().length > 1) {
|
||||
listener.onFailure(new IllegalStateException("multiple tokens share the same refresh token"));
|
||||
} else {
|
||||
listener.onResponse(new Tuple<>(searchResponse, attemptCount));
|
||||
}
|
||||
}, e -> {
|
||||
if (isShardNotAvailableException(e)) {
|
||||
logger.debug("failed to search for token document, retrying", e);
|
||||
attemptCount.incrementAndGet();
|
||||
findTokenFromRefreshToken(refreshToken, listener, attemptCount);
|
||||
} else {
|
||||
listener.onFailure(e);
|
||||
}
|
||||
}),
|
||||
client::search));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -701,6 +800,81 @@ public final class TokenService extends AbstractComponent {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all stored refresh and access tokens that have not been invalidated or expired, and were issued against
|
||||
* the specified realm.
|
||||
*/
|
||||
public void findActiveTokensForRealm(String realmName, ActionListener<List<Tuple<UserToken, String>>> listener) {
|
||||
ensureEnabled();
|
||||
|
||||
if (Strings.isNullOrEmpty(realmName)) {
|
||||
listener.onFailure(new IllegalArgumentException("Realm name is required"));
|
||||
return;
|
||||
}
|
||||
|
||||
final Instant now = clock.instant();
|
||||
final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
|
||||
.filter(QueryBuilders.termQuery("doc_type", "token"))
|
||||
.filter(QueryBuilders.termQuery("access_token.realm", realmName))
|
||||
.filter(QueryBuilders.boolQuery()
|
||||
.should(QueryBuilders.boolQuery()
|
||||
.must(QueryBuilders.termQuery("access_token.invalidated", false))
|
||||
.must(QueryBuilders.rangeQuery("access_token.user_token.expiration_time").gte(now.toEpochMilli()))
|
||||
)
|
||||
.should(QueryBuilders.termQuery("refresh_token.invalidated", false))
|
||||
);
|
||||
|
||||
SearchRequest request = client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME)
|
||||
.setQuery(boolQuery)
|
||||
.setVersion(false)
|
||||
.request();
|
||||
|
||||
lifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () ->
|
||||
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
|
||||
ActionListener.<SearchResponse>wrap(searchResponse -> {
|
||||
if (searchResponse.isTimedOut()) {
|
||||
listener.onFailure(new ElasticsearchSecurityException("Failed to find user tokens"));
|
||||
} else {
|
||||
listener.onResponse(parseDocuments(searchResponse));
|
||||
}
|
||||
}, listener::onFailure),
|
||||
client::search));
|
||||
}
|
||||
|
||||
private List<Tuple<UserToken, String>> parseDocuments(SearchResponse searchResponse) {
|
||||
return StreamSupport.stream(searchResponse.getHits().spliterator(), false).map(hit -> {
|
||||
final Map<String, Object> source = hit.getSourceAsMap();
|
||||
try {
|
||||
return parseTokensFromDocument(source);
|
||||
} catch (IOException e) {
|
||||
throw invalidGrantException("cannot read token from document");
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link Tuple} of access-token and refresh-token-id
|
||||
*/
|
||||
private Tuple<UserToken, String> parseTokensFromDocument(Map<String, Object> source) throws IOException {
|
||||
final String refreshToken = (String) ((Map<String, Object>) source.get("refresh_token")).get("token");
|
||||
|
||||
final Map<String, Object> userTokenSource = (Map<String, Object>)
|
||||
((Map<String, Object>) source.get("access_token")).get("user_token");
|
||||
final String id = (String) userTokenSource.get("id");
|
||||
final Integer version = (Integer) userTokenSource.get("version");
|
||||
final String authString = (String) userTokenSource.get("authentication");
|
||||
final Long expiration = (Long) userTokenSource.get("expiration_time");
|
||||
final Map<String, Object> metadata = (Map<String, Object>) userTokenSource.get("metadata");
|
||||
|
||||
Version authVersion = Version.fromId(version);
|
||||
try (StreamInput in = StreamInput.wrap(Base64.getDecoder().decode(authString))) {
|
||||
in.setVersion(authVersion);
|
||||
Authentication authentication = new Authentication(in);
|
||||
return new Tuple<>(new UserToken(id, Version.fromId(version), authentication, Instant.ofEpochMilli(expiration), metadata),
|
||||
refreshToken);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getInvalidatedTokenDocumentId(UserToken userToken) {
|
||||
return getInvalidatedTokenDocumentId(userToken.getId());
|
||||
}
|
||||
|
@ -765,6 +939,8 @@ public final class TokenService extends AbstractComponent {
|
|||
listener.onResponse(userToken);
|
||||
}
|
||||
}
|
||||
} else if (userToken.getVersion().onOrAfter(Version.V_6_2_0)) {
|
||||
listener.onFailure(new IllegalStateException("token document is missing and must be present"));
|
||||
} else {
|
||||
listener.onResponse(userToken);
|
||||
}
|
||||
|
@ -787,6 +963,7 @@ public final class TokenService extends AbstractComponent {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public TimeValue getExpirationDelay() {
|
||||
return expirationDelay;
|
||||
}
|
||||
|
@ -840,7 +1017,11 @@ public final class TokenService extends AbstractComponent {
|
|||
new CipherOutputStream(out, getEncryptionCipher(initializationVector, keyAndCache, userToken.getVersion()));
|
||||
StreamOutput encryptedStreamOutput = new OutputStreamStreamOutput(encryptedOutput)) {
|
||||
encryptedStreamOutput.setVersion(userToken.getVersion());
|
||||
userToken.writeTo(encryptedStreamOutput);
|
||||
if (userToken.getVersion().onOrAfter(Version.V_6_2_0)) {
|
||||
encryptedStreamOutput.writeString(userToken.getId());
|
||||
} else {
|
||||
userToken.writeTo(encryptedStreamOutput);
|
||||
}
|
||||
encryptedStreamOutput.close();
|
||||
return new String(os.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
@ -1047,6 +1228,7 @@ public final class TokenService extends AbstractComponent {
|
|||
|
||||
/**
|
||||
* Prunes the keys and keeps up to the latest N keys around
|
||||
*
|
||||
* @param numKeysToKeep the number of keys to keep.
|
||||
*/
|
||||
synchronized TokenMetaData pruneKeys(int numKeysToKeep) {
|
||||
|
@ -1058,8 +1240,8 @@ public final class TokenService extends AbstractComponent {
|
|||
ArrayList<KeyAndCache> entries = new ArrayList<>(keyCache.cache.values());
|
||||
Collections.sort(entries,
|
||||
(left, right) -> Long.compare(right.keyAndTimestamp.getTimestamp(), left.keyAndTimestamp.getTimestamp()));
|
||||
for (KeyAndCache value: entries) {
|
||||
if (map.size() < numKeysToKeep || value.keyAndTimestamp.getTimestamp()>= currentKey
|
||||
for (KeyAndCache value : entries) {
|
||||
if (map.size() < numKeysToKeep || value.keyAndTimestamp.getTimestamp() >= currentKey
|
||||
.keyAndTimestamp.getTimestamp()) {
|
||||
logger.debug("keeping key {} ", value.getKeyHash());
|
||||
map.put(value.getKeyHash(), value);
|
||||
|
@ -1095,7 +1277,7 @@ public final class TokenService extends AbstractComponent {
|
|||
byte[] saltArr = new byte[SALT_BYTES];
|
||||
Map<BytesKey, KeyAndCache> map = new HashMap<>(metaData.getKeys().size());
|
||||
long maxTimestamp = createdTimeStamps.get();
|
||||
for (KeyAndTimestamp key : metaData.getKeys()) {
|
||||
for (KeyAndTimestamp key : metaData.getKeys()) {
|
||||
secureRandom.nextBytes(saltArr);
|
||||
KeyAndCache keyAndCache = new KeyAndCache(key, new BytesKey(saltArr));
|
||||
maxTimestamp = Math.max(keyAndCache.keyAndTimestamp.getTimestamp(), maxTimestamp);
|
||||
|
@ -1252,7 +1434,7 @@ public final class TokenService extends AbstractComponent {
|
|||
}
|
||||
|
||||
BytesKey getKeyHash() {
|
||||
return keyHash;
|
||||
return keyHash;
|
||||
}
|
||||
|
||||
private static BytesKey calculateKeyHash(SecureString key) {
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -44,13 +45,24 @@ public final class UserToken implements Writeable, ToXContentObject {
|
|||
this(Version.CURRENT, authentication, expirationTime, Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new token with an autogenerated id
|
||||
*/
|
||||
UserToken(Version version, Authentication authentication, Instant expirationTime, Map<String, Object> metadata) {
|
||||
this.version = version;
|
||||
this.id = UUIDs.base64UUID();
|
||||
this(UUIDs.randomBase64UUID(), version, authentication, expirationTime, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new token from an existing id
|
||||
*/
|
||||
UserToken(String id, Version version, Authentication authentication, Instant expirationTime, Map<String, Object> metadata) {
|
||||
this.version = Objects.requireNonNull(version);
|
||||
this.id = Objects.requireNonNull(id);
|
||||
this.authentication = Objects.requireNonNull(authentication);
|
||||
this.expirationTime = Objects.requireNonNull(expirationTime);
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new token based on the values from the stream
|
||||
*/
|
||||
|
@ -126,4 +138,18 @@ public final class UserToken implements Writeable, ToXContentObject {
|
|||
}
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
static UserToken fromSourceMap(Map<String, Object> source) throws IOException {
|
||||
final String id = (String) source.get("id");
|
||||
final Long expirationEpochMilli = (Long) source.get("expiration_time");
|
||||
final Integer versionId = (Integer) source.get("version");
|
||||
final Map<String, Object> metadata = (Map<String, Object>) source.get("metadata");
|
||||
final String authString = (String) source.get("authentication");
|
||||
final Version version = Version.fromId(versionId);
|
||||
try (StreamInput in = StreamInput.wrap(Base64.getDecoder().decode(authString))) {
|
||||
in.setVersion(version);
|
||||
Authentication authentication = new Authentication(in);
|
||||
return new UserToken(id, version, authentication, Instant.ofEpochMilli(expirationEpochMilli), metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.opensaml.security.credential.Credential;
|
||||
|
||||
/**
|
||||
* A simple container class that holds all configuration related to a SAML Identity Provider (IdP).
|
||||
*/
|
||||
class IdpConfiguration {
|
||||
|
||||
private final String entityId;
|
||||
private final Supplier<List<Credential>> signingCredentials;
|
||||
|
||||
IdpConfiguration(String entityId, Supplier<List<Credential>> signingCredentials) {
|
||||
this.entityId = entityId;
|
||||
this.signingCredentials = signingCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SAML identifier (as a URI) for the IDP
|
||||
*/
|
||||
String getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of credentials that the IDP uses for signing messages.
|
||||
* A trusted message should be signed with any one (or more) of these credentials.
|
||||
*/
|
||||
List<Credential> getSigningCredentials() {
|
||||
return signingCredentials.get();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.inject.internal.Nullable;
|
||||
import org.opensaml.saml.saml2.core.Attribute;
|
||||
import org.opensaml.saml.saml2.core.NameIDType;
|
||||
|
||||
/**
|
||||
* An lightweight collection of SAML attributes
|
||||
*/
|
||||
public class SamlAttributes {
|
||||
|
||||
public static final String NAMEID_SYNTHENTIC_ATTRIBUTE = "nameid";
|
||||
public static final String PERSISTENT_NAMEID_SYNTHENTIC_ATTRIBUTE = "nameid:persistent";
|
||||
|
||||
private final SamlNameId name;
|
||||
private final String session;
|
||||
private final List<SamlAttribute> attributes;
|
||||
|
||||
SamlAttributes(SamlNameId name, String session, List<SamlAttribute> attributes) {
|
||||
this.name = name;
|
||||
this.session = session;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all values for the specified attribute
|
||||
*
|
||||
* @param attributeId The name of the attribute - either its {@code name} or @{code friendlyName}
|
||||
* @return A list of all matching attribute values (may be empty).
|
||||
*/
|
||||
List<String> getAttributeValues(String attributeId) {
|
||||
if (attributeId.equals(NAMEID_SYNTHENTIC_ATTRIBUTE)) {
|
||||
return name == null ? Collections.emptyList() : Collections.singletonList(name.value);
|
||||
}
|
||||
if (name != null && NameIDType.PERSISTENT.equals(name.format) && attributeId.equals(PERSISTENT_NAMEID_SYNTHENTIC_ATTRIBUTE)) {
|
||||
return Collections.singletonList(name.value);
|
||||
}
|
||||
if (Strings.isNullOrEmpty(attributeId)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return attributes.stream()
|
||||
.filter(attr -> attributeId.equals(attr.name) || attributeId.equals(attr.friendlyName))
|
||||
.flatMap(attr -> attr.values.stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<SamlAttribute> attributes() {
|
||||
return Collections.unmodifiableList(attributes);
|
||||
}
|
||||
|
||||
SamlNameId name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
String session() {
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "(" + name + ")[" + session + "]{" + attributes + "}";
|
||||
}
|
||||
|
||||
static class SamlAttribute {
|
||||
final String name;
|
||||
final String friendlyName;
|
||||
final List<String> values;
|
||||
|
||||
SamlAttribute(Attribute attribute) {
|
||||
this(attribute.getName(), attribute.getFriendlyName(),
|
||||
attribute.getAttributeValues().stream().map(x -> x.getDOM().getTextContent()).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
SamlAttribute(String name, @Nullable String friendlyName, List<String> values) {
|
||||
this.name = Objects.requireNonNull(name, "Attribute name cannot be null");
|
||||
this.friendlyName = friendlyName;
|
||||
this.values = Collections.unmodifiableList(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (Strings.isNullOrEmpty(friendlyName)) {
|
||||
return name + '=' + values;
|
||||
} else {
|
||||
return friendlyName + '(' + name + ")=" + values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.opensaml.core.xml.XMLObject;
|
||||
import org.opensaml.saml.saml2.core.Assertion;
|
||||
import org.opensaml.saml.saml2.core.Attribute;
|
||||
import org.opensaml.saml.saml2.core.AttributeStatement;
|
||||
import org.opensaml.saml.saml2.core.Audience;
|
||||
import org.opensaml.saml.saml2.core.AudienceRestriction;
|
||||
import org.opensaml.saml.saml2.core.Conditions;
|
||||
import org.opensaml.saml.saml2.core.EncryptedAssertion;
|
||||
import org.opensaml.saml.saml2.core.EncryptedAttribute;
|
||||
import org.opensaml.saml.saml2.core.Response;
|
||||
import org.opensaml.saml.saml2.core.Status;
|
||||
import org.opensaml.saml.saml2.core.StatusCode;
|
||||
import org.opensaml.saml.saml2.core.StatusDetail;
|
||||
import org.opensaml.saml.saml2.core.StatusMessage;
|
||||
import org.opensaml.saml.saml2.core.Subject;
|
||||
import org.opensaml.saml.saml2.core.SubjectConfirmation;
|
||||
import org.opensaml.saml.saml2.core.SubjectConfirmationData;
|
||||
import org.opensaml.xmlsec.encryption.support.DecryptionException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import static org.elasticsearch.xpack.security.authc.saml.SamlUtils.samlException;
|
||||
import static org.opensaml.saml.saml2.core.SubjectConfirmation.METHOD_BEARER;
|
||||
|
||||
/**
|
||||
* Processes the IdP's SAML Response for our AuthnRequest, validates it, and extracts the relevant properties.
|
||||
*/
|
||||
class SamlAuthenticator extends SamlRequestHandler {
|
||||
|
||||
private static final String RESPONSE_TAG_NAME = "Response";
|
||||
|
||||
SamlAuthenticator(RealmConfig realmConfig,
|
||||
Clock clock,
|
||||
IdpConfiguration idp,
|
||||
SpConfiguration sp,
|
||||
TimeValue maxSkew) {
|
||||
super(realmConfig, clock, idp, sp, maxSkew);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the provided SAML response within the provided token and, if valid, extracts the relevant attributes from it.
|
||||
*
|
||||
* @throws org.elasticsearch.ElasticsearchSecurityException If the SAML is invalid for this realm/configuration
|
||||
*/
|
||||
SamlAttributes authenticate(SamlToken token) {
|
||||
final Element root = parseSamlMessage(token.getContent());
|
||||
if (RESPONSE_TAG_NAME.equals(root.getLocalName()) && SAML_NAMESPACE.equals(root.getNamespaceURI())) {
|
||||
try {
|
||||
return authenticateResponse(root, token.getAllowedSamlRequestIds());
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
logger.trace("Rejecting SAML response {} because {}", SamlUtils.toString(root), e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw samlException("SAML content [{}] should have a root element of Namespace=[{}] Tag=[{}]",
|
||||
root, SAML_NAMESPACE, RESPONSE_TAG_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
private SamlAttributes authenticateResponse(Element element, Collection<String> allowedSamlRequestIds) {
|
||||
final Response response = buildXmlObject(element, Response.class);
|
||||
if (response == null) {
|
||||
throw samlException("Cannot convert element {} into Response object", element);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(SamlUtils.describeSamlObject(response));
|
||||
}
|
||||
if (Strings.hasText(response.getInResponseTo()) && allowedSamlRequestIds.contains(response.getInResponseTo()) == false) {
|
||||
logger.debug("The SAML Response with ID {} is unsolicited. A user might have used a stale URL or the Identity Provider " +
|
||||
"incorrectly populates the InResponseTo attribute", response.getID());
|
||||
throw samlException("SAML content is in-response-to {} but expected one of {} ",
|
||||
response.getInResponseTo(), allowedSamlRequestIds);
|
||||
}
|
||||
|
||||
final Status status = response.getStatus();
|
||||
if (status == null || status.getStatusCode() == null) {
|
||||
throw samlException("SAML Response has no status code");
|
||||
}
|
||||
if (isSuccess(status) == false) {
|
||||
throw samlException("SAML Response is not a 'success' response: Code={} Message={} Detail={}",
|
||||
status.getStatusCode().getValue(), getMessage(status), getDetail(status));
|
||||
}
|
||||
|
||||
checkResponseDestination(response);
|
||||
|
||||
Tuple<Assertion, List<Attribute>> details = extractDetails(response, allowedSamlRequestIds);
|
||||
final Assertion assertion = details.v1();
|
||||
final SamlNameId nameId = SamlNameId.forSubject(assertion.getSubject());
|
||||
final String session = getSessionIndex(assertion);
|
||||
final List<SamlAttributes.SamlAttribute> attributes = details.v2().stream()
|
||||
.map(SamlAttributes.SamlAttribute::new)
|
||||
.collect(Collectors.toList());
|
||||
if (logger.isTraceEnabled()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("The SAML Assertion contained the following attributes: \n");
|
||||
for (SamlAttributes.SamlAttribute attr : attributes) {
|
||||
sb.append(attr).append("\n");
|
||||
}
|
||||
logger.trace(sb.toString());
|
||||
}
|
||||
if (attributes.isEmpty() && nameId == null) {
|
||||
logger.debug("The Attribute Statements of SAML Response with ID {} contained no attributes and the SAML Assertion Subject did" +
|
||||
"not contain a SAML NameID. Please verify that the Identity Provider configuration with regards to attribute " +
|
||||
"release is correct. ", response.getID());
|
||||
throw samlException("Could not process any SAML attributes in {}", response.getElementQName());
|
||||
}
|
||||
|
||||
return new SamlAttributes(nameId, session, attributes);
|
||||
}
|
||||
|
||||
private String getMessage(Status status) {
|
||||
final StatusMessage sm = status.getStatusMessage();
|
||||
return sm == null ? null : sm.getMessage();
|
||||
}
|
||||
|
||||
private String getDetail(Status status) {
|
||||
final StatusDetail sd = status.getStatusDetail();
|
||||
return sd == null ? null : SamlUtils.toString(sd.getDOM());
|
||||
}
|
||||
|
||||
private boolean isSuccess(Status status) {
|
||||
return status.getStatusCode().getValue().equals(StatusCode.SUCCESS);
|
||||
}
|
||||
|
||||
private String getSessionIndex(Assertion assertion) {
|
||||
return assertion.getAuthnStatements().stream().map(as -> as.getSessionIndex()).filter(Objects::nonNull).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private void checkResponseDestination(Response response) {
|
||||
final String asc = getSpConfiguration().getAscUrl();
|
||||
if (asc.equals(response.getDestination()) == false) {
|
||||
throw samlException("SAML response " + response.getID() + " is for destination " + response.getDestination()
|
||||
+ " but this realm uses " + asc);
|
||||
}
|
||||
}
|
||||
|
||||
private Tuple<Assertion, List<Attribute>> extractDetails(Response response, Collection<String> allowedSamlRequestIds) {
|
||||
final boolean requireSignedAssertions;
|
||||
if (response.isSigned()) {
|
||||
validateSignature(response.getSignature());
|
||||
requireSignedAssertions = false;
|
||||
} else {
|
||||
requireSignedAssertions = true;
|
||||
}
|
||||
|
||||
checkIssuer(response.getIssuer(), response);
|
||||
|
||||
final int assertionCount = response.getAssertions().size() + response.getEncryptedAssertions().size();
|
||||
if (assertionCount > 1) {
|
||||
throw samlException("Expecting only 1 assertion, but response contains multiple (" + assertionCount + ")");
|
||||
}
|
||||
for (Assertion assertion : response.getAssertions()) {
|
||||
return new Tuple<>(assertion, processAssertion(assertion, requireSignedAssertions, allowedSamlRequestIds));
|
||||
}
|
||||
for (EncryptedAssertion encrypted : response.getEncryptedAssertions()) {
|
||||
Assertion assertion = decrypt(encrypted);
|
||||
moveToNewDocument(assertion);
|
||||
assertion.getDOM().setIdAttribute("ID", true);
|
||||
return new Tuple<>(assertion, processAssertion(assertion, requireSignedAssertions, allowedSamlRequestIds));
|
||||
}
|
||||
throw samlException("No assertions found in SAML response");
|
||||
}
|
||||
|
||||
private void moveToNewDocument(XMLObject xmlObject) {
|
||||
final Element element = xmlObject.getDOM();
|
||||
final Document doc = element.getOwnerDocument().getImplementation().createDocument(null, null, null);
|
||||
doc.adoptNode(element);
|
||||
doc.appendChild(element);
|
||||
}
|
||||
|
||||
private Assertion decrypt(EncryptedAssertion encrypted) {
|
||||
if (decrypter == null) {
|
||||
throw samlException("SAML assertion [" + text(encrypted, 32) + "] is encrypted, but no decryption key is available");
|
||||
}
|
||||
try {
|
||||
return decrypter.decrypt(encrypted);
|
||||
} catch (DecryptionException e) {
|
||||
logger.debug(() -> new ParameterizedMessage("Failed to decrypt SAML assertion [{}] with [{}]",
|
||||
text(encrypted, 512), describe(getSpConfiguration().getEncryptionCredential().getEntityCertificate())), e);
|
||||
throw samlException("Failed to decrypt SAML assertion " + text(encrypted, 32), e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Attribute> processAssertion(Assertion assertion, boolean requireSignature, Collection<String> allowedSamlRequestIds) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("(Possibly decrypted) Assertion: {}", SamlUtils.samlObjectToString(assertion));
|
||||
logger.trace(SamlUtils.describeSamlObject(assertion));
|
||||
}
|
||||
// Do not further process unsigned Assertions
|
||||
if (assertion.isSigned()) {
|
||||
validateSignature(assertion.getSignature());
|
||||
} else if (requireSignature) {
|
||||
throw samlException("Assertion [{}] is not signed, but a signature is required", assertion.getElementQName());
|
||||
}
|
||||
|
||||
checkConditions(assertion.getConditions());
|
||||
checkIssuer(assertion.getIssuer(), assertion);
|
||||
checkSubject(assertion.getSubject(), assertion, allowedSamlRequestIds);
|
||||
|
||||
List<Attribute> attributes = new ArrayList<>();
|
||||
for (AttributeStatement statement : assertion.getAttributeStatements()) {
|
||||
logger.trace("SAML AttributeStatement has [{}] attributes and [{}] encrypted attributes",
|
||||
statement.getAttributes().size(), statement.getEncryptedAttributes().size());
|
||||
attributes.addAll(statement.getAttributes());
|
||||
for (EncryptedAttribute enc : statement.getEncryptedAttributes()) {
|
||||
final Attribute attribute = decrypt(enc);
|
||||
if (attribute != null) {
|
||||
logger.trace("Successfully decrypted attribute: {}" + SamlUtils.samlObjectToString(attribute));
|
||||
attributes.add(attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private Attribute decrypt(EncryptedAttribute encrypted) {
|
||||
if (decrypter == null) {
|
||||
logger.info("SAML message has encrypted attribute [" + text(encrypted, 32) + "], but no encryption key has been configured");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return decrypter.decrypt(encrypted);
|
||||
} catch (DecryptionException e) {
|
||||
logger.info("Failed to decrypt SAML attribute " + text(encrypted, 32), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkConditions(Conditions conditions) {
|
||||
if (conditions != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("SAML Assertion was intended for the following Service providers: {}",
|
||||
conditions.getAudienceRestrictions().stream().map(r -> text(r, 32))
|
||||
.collect(Collectors.joining(" | ")));
|
||||
logger.trace("SAML Assertion is only valid between: " + conditions.getNotBefore() + " and " + conditions.getNotOnOrAfter());
|
||||
}
|
||||
checkAudienceRestrictions(conditions.getAudienceRestrictions());
|
||||
checkLifetimeRestrictions(conditions);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSubject(Subject assertionSubject, XMLObject parent, Collection<String> allowedSamlRequestIds) {
|
||||
|
||||
if (assertionSubject == null) {
|
||||
throw samlException("SAML Assertion ({}) has no Subject", text(parent, 16));
|
||||
}
|
||||
final List<SubjectConfirmationData> confirmationData = assertionSubject.getSubjectConfirmations().stream()
|
||||
.filter(data -> data.getMethod().equals(METHOD_BEARER))
|
||||
.map(SubjectConfirmation::getSubjectConfirmationData).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
if (confirmationData.size() != 1) {
|
||||
throw samlException("SAML Assertion subject contains {} bearer SubjectConfirmation, while exactly one was expected.",
|
||||
confirmationData.size());
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("SAML Assertion Subject Confirmation intended recipient is: " + confirmationData.get(0).getRecipient());
|
||||
logger.trace("SAML Assertion Subject Confirmation is only valid before: " + confirmationData.get(0).getNotOnOrAfter());
|
||||
logger.trace("SAML Assertion Subject Confirmation is in response to: " + confirmationData.get(0).getInResponseTo());
|
||||
}
|
||||
checkRecipient(confirmationData.get(0));
|
||||
checkLifetimeRestrictions(confirmationData.get(0));
|
||||
checkInResponseTo(confirmationData.get(0), allowedSamlRequestIds);
|
||||
}
|
||||
|
||||
private void checkRecipient(SubjectConfirmationData subjectConfirmationData) {
|
||||
final SpConfiguration sp = getSpConfiguration();
|
||||
if (sp.getAscUrl().equals(subjectConfirmationData.getRecipient()) == false) {
|
||||
throw samlException("SAML Assertion SubjectConfirmationData Recipient {} does not match expected value {}",
|
||||
subjectConfirmationData.getRecipient(), sp.getAscUrl());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkInResponseTo(SubjectConfirmationData subjectConfirmationData, Collection<String> allowedSamlRequestIds) {
|
||||
// Allow for IdP initiated SSO where InResponseTo MUST be missing
|
||||
if (Strings.hasText(subjectConfirmationData.getInResponseTo())
|
||||
&& allowedSamlRequestIds.contains(subjectConfirmationData.getInResponseTo()) == false) {
|
||||
throw samlException("SAML Assertion SubjectConfirmationData is in-response-to {} but expected one of {} ",
|
||||
subjectConfirmationData.getInResponseTo(), allowedSamlRequestIds);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAudienceRestrictions(List<AudienceRestriction> restrictions) {
|
||||
final String spEntityId = this.getSpConfiguration().getEntityId();
|
||||
final Predicate<AudienceRestriction> predicate = ar ->
|
||||
ar.getAudiences().stream().map(Audience::getAudienceURI).anyMatch(spEntityId::equals);
|
||||
if (restrictions.stream().allMatch(predicate) == false) {
|
||||
throw samlException("Conditions [{}] do not match required audience [{}]",
|
||||
restrictions.stream().map(r -> text(r, 32)).collect(Collectors.joining(" | ")), getSpConfiguration().getEntityId());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkLifetimeRestrictions(Conditions conditions) {
|
||||
// In order to compensate for clock skew we construct 2 alternate realities
|
||||
// - a "future now" that is now + the maximum skew we will tolerate. Essentially "if our clock is 2min slow, what time is it now?"
|
||||
// - a "past now" that is now - the maximum skew we will tolerate. Essentially "if our clock is 2min fast, what time is it now?"
|
||||
final Instant now = now();
|
||||
final Instant futureNow = now.plusMillis(maxSkewInMillis());
|
||||
final Instant pastNow = now.minusMillis(maxSkewInMillis());
|
||||
if (conditions.getNotBefore() != null && futureNow.isBefore(toInstant(conditions.getNotBefore()))) {
|
||||
throw samlException("Rejecting SAML assertion because [{}] is before [{}]", futureNow, conditions.getNotBefore());
|
||||
}
|
||||
if (conditions.getNotOnOrAfter() != null && pastNow.isBefore(toInstant(conditions.getNotOnOrAfter())) == false) {
|
||||
throw samlException("Rejecting SAML assertion because [{}] is on/after [{}]", pastNow, conditions.getNotOnOrAfter());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkLifetimeRestrictions(SubjectConfirmationData subjectConfirmationData) {
|
||||
validateNotOnOrAfter(subjectConfirmationData.getNotOnOrAfter());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.opensaml.saml.saml2.core.Issuer;
|
||||
import org.opensaml.saml.saml2.core.NameIDPolicy;
|
||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
|
||||
|
||||
/**
|
||||
* Generates a SAML {@link AuthnRequest} from a simplified set of parameters.
|
||||
*/
|
||||
class SamlAuthnRequestBuilder extends SamlMessageBuilder {
|
||||
|
||||
private final String spBinding;
|
||||
private final String idpBinding;
|
||||
private String nameIdFormat;
|
||||
private Boolean forceAuthn;
|
||||
|
||||
SamlAuthnRequestBuilder(SpConfiguration spConfig, String spBinding, EntityDescriptor idpDescriptor, String idBinding, Clock clock) {
|
||||
super(idpDescriptor, spConfig, clock);
|
||||
this.spBinding = spBinding;
|
||||
this.idpBinding = idBinding;
|
||||
}
|
||||
|
||||
SamlAuthnRequestBuilder nameIdFormat(String nameIdFormat) {
|
||||
this.nameIdFormat = nameIdFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
SamlAuthnRequestBuilder forceAuthn(Boolean forceAuthn) {
|
||||
this.forceAuthn = forceAuthn;
|
||||
return this;
|
||||
}
|
||||
|
||||
AuthnRequest build() {
|
||||
final String destination = getIdpLocation();
|
||||
|
||||
final AuthnRequest request = SamlUtils.buildObject(AuthnRequest.class, AuthnRequest.DEFAULT_ELEMENT_NAME);
|
||||
request.setID(buildId());
|
||||
request.setIssueInstant(now());
|
||||
request.setDestination(destination);
|
||||
request.setProtocolBinding(spBinding);
|
||||
request.setAssertionConsumerServiceURL(serviceProvider.getAscUrl());
|
||||
request.setIssuer(buildIssuer());
|
||||
request.setNameIDPolicy(buildNameIDPolicy());
|
||||
request.setForceAuthn(forceAuthn);
|
||||
return request;
|
||||
}
|
||||
|
||||
private NameIDPolicy buildNameIDPolicy() {
|
||||
NameIDPolicy nameIDPolicy = SamlUtils.buildObject(NameIDPolicy.class, NameIDPolicy.DEFAULT_ELEMENT_NAME);
|
||||
nameIDPolicy.setFormat(nameIdFormat);
|
||||
nameIDPolicy.setSPNameQualifier(serviceProvider.getEntityId());
|
||||
nameIDPolicy.setAllowCreate(false);
|
||||
return nameIDPolicy;
|
||||
}
|
||||
|
||||
private String getIdpLocation() {
|
||||
final String location = getIdentityProviderEndpoint(idpBinding, IDPSSODescriptor::getSingleSignOnServices);
|
||||
if (location == null) {
|
||||
throw new ElasticsearchException("Cannot find [{}]/[{}] in descriptor [{}]",
|
||||
IDPSSODescriptor.DEFAULT_ELEMENT_NAME, idpBinding, identityProvider.getID());
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Clock;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.io.Streams;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.rest.RestUtils;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.opensaml.saml.common.SAMLObject;
|
||||
import org.opensaml.saml.saml2.core.EncryptedID;
|
||||
import org.opensaml.saml.saml2.core.LogoutRequest;
|
||||
import org.opensaml.saml.saml2.core.NameID;
|
||||
import org.opensaml.xmlsec.crypto.XMLSigningUtil;
|
||||
import org.opensaml.xmlsec.encryption.support.DecryptionException;
|
||||
import org.opensaml.xmlsec.signature.Signature;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import static org.elasticsearch.xpack.security.authc.saml.SamlUtils.samlException;
|
||||
|
||||
/**
|
||||
* Processes a LogoutRequest for an IdP-initiated logout.
|
||||
*/
|
||||
public class SamlLogoutRequestHandler extends SamlRequestHandler {
|
||||
|
||||
private static final String REQUEST_TAG_NAME = "LogoutRequest";
|
||||
|
||||
SamlLogoutRequestHandler(RealmConfig realmConfig, Clock clock, IdpConfiguration idp, SpConfiguration sp, TimeValue maxSkew) {
|
||||
super(realmConfig, clock, idp, sp, maxSkew);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the provided LogoutRequest and extracts the NameID and SessionIndex.
|
||||
* Returns these in a {@link SamlAttributes} object with an empty attributes list.
|
||||
* <p>
|
||||
* The recommended binding for Logout (for maximum interoperability) is HTTP-Redirect.
|
||||
* Under this binding the signature is applied to the query-string (including parameter
|
||||
* names and url-encoded/base64-encoded/deflated values). Therefore in order to properly
|
||||
* validate the signature, this method operates on a raw query- string.
|
||||
*
|
||||
* @throws ElasticsearchSecurityException If the SAML is invalid for this realm/configuration
|
||||
*/
|
||||
public Result parseFromQueryString(String queryString) {
|
||||
final ParsedQueryString parsed = parseQueryStringAndValidateSignature(queryString);
|
||||
|
||||
final Element root = parseSamlMessage(inflate(decodeBase64(parsed.samlRequest)));
|
||||
if (REQUEST_TAG_NAME.equals(root.getLocalName()) && SAML_NAMESPACE.equals(root.getNamespaceURI())) {
|
||||
try {
|
||||
final LogoutRequest logoutRequest = buildXmlObject(root, LogoutRequest.class);
|
||||
return parseLogout(logoutRequest, parsed.hasSignature == false, parsed.relayState);
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
logger.trace("Rejecting SAML logout request {} because {}", SamlUtils.toString(root), e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw samlException("SAML content [{}] should have a root element of Namespace=[{}] Tag=[{}]",
|
||||
root, SAML_NAMESPACE, REQUEST_TAG_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
private ParsedQueryString parseQueryStringAndValidateSignature(String queryString) {
|
||||
final String signatureInput = queryString.replaceAll("&Signature=.*$", "");
|
||||
final Map<String, String> parameters = new HashMap<>();
|
||||
RestUtils.decodeQueryString(queryString, 0, parameters);
|
||||
final String samlRequest = parameters.get("SAMLRequest");
|
||||
if (samlRequest == null) {
|
||||
throw samlException("Could not parse SAMLRequest from query string: [{}]", queryString);
|
||||
}
|
||||
|
||||
final String relayState = parameters.get("RelayState");
|
||||
final String signatureAlgorithm = parameters.get("SigAlg");
|
||||
final String signature = parameters.get("Signature");
|
||||
if (signature == null || signatureAlgorithm == null) {
|
||||
return new ParsedQueryString(samlRequest, false, relayState);
|
||||
}
|
||||
|
||||
validateSignature(signatureInput, signatureAlgorithm, signature);
|
||||
return new ParsedQueryString(samlRequest, true, relayState);
|
||||
}
|
||||
|
||||
private Result parseLogout(LogoutRequest logoutRequest, boolean requireSignature, String relayState) {
|
||||
final Signature signature = logoutRequest.getSignature();
|
||||
if (signature == null) {
|
||||
if (requireSignature) {
|
||||
throw samlException("Logout request is not signed");
|
||||
}
|
||||
} else {
|
||||
validateSignature(signature);
|
||||
}
|
||||
|
||||
checkIssuer(logoutRequest.getIssuer(), logoutRequest);
|
||||
checkDestination(logoutRequest);
|
||||
validateNotOnOrAfter(logoutRequest.getNotOnOrAfter());
|
||||
|
||||
return new Result(logoutRequest.getID(), SamlNameId.fromXml(getNameID(logoutRequest)), getSessionIndex(logoutRequest), relayState);
|
||||
}
|
||||
|
||||
private void validateSignature(String inputString, String signatureAlgorithm, String signature) {
|
||||
final byte[] sigBytes = decodeBase64(signature);
|
||||
final byte[] inputBytes = inputString.getBytes(StandardCharsets.US_ASCII);
|
||||
final String signatureText = Strings.cleanTruncate(signature, 32);
|
||||
checkIdpSignature(credential -> {
|
||||
if (XMLSigningUtil.verifyWithURI(credential, signatureAlgorithm, sigBytes, inputBytes)) {
|
||||
logger.debug(() -> new ParameterizedMessage("SAML Signature [{}] matches credentials [{}] [{}]",
|
||||
signatureText, credential.getEntityId(), credential.getPublicKey()));
|
||||
return true;
|
||||
} else {
|
||||
logger.debug(() -> new ParameterizedMessage("SAML Signature [{}] failed against credentials [{}] [{}]",
|
||||
signatureText, credential.getEntityId(), credential.getPublicKey()));
|
||||
return false;
|
||||
}
|
||||
}, signatureText);
|
||||
}
|
||||
|
||||
private byte[] decodeBase64(String content) {
|
||||
try {
|
||||
return Base64.getDecoder().decode(content.replaceAll("\\s+", ""));
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.info("Failed to decode base64 string [{}] - {}", content, e.toString());
|
||||
throw samlException("SAML message cannot be Base64 decoded", e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] inflate(byte[] bytes) {
|
||||
Inflater inflater = new Inflater(true);
|
||||
try (ByteArrayInputStream in = new ByteArrayInputStream(bytes);
|
||||
InflaterInputStream inflate = new InflaterInputStream(in, inflater);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(bytes.length * 3 / 2)) {
|
||||
Streams.copy(inflate, out);
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw samlException("SAML message cannot be inflated", e);
|
||||
}
|
||||
}
|
||||
|
||||
private NameID getNameID(LogoutRequest logoutRequest) {
|
||||
final NameID nameID = logoutRequest.getNameID();
|
||||
if (nameID == null) {
|
||||
final EncryptedID encryptedID = logoutRequest.getEncryptedID();
|
||||
if (encryptedID != null) {
|
||||
final SAMLObject samlObject = decrypt(encryptedID);
|
||||
if (samlObject instanceof NameID) {
|
||||
return (NameID) samlObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nameID;
|
||||
}
|
||||
|
||||
private SAMLObject decrypt(EncryptedID encrypted) {
|
||||
if (decrypter == null) {
|
||||
throw samlException("SAML EncryptedID [" + text(encrypted, 32) + "] is encrypted, but no decryption key is available");
|
||||
}
|
||||
try {
|
||||
return decrypter.decrypt(encrypted);
|
||||
} catch (DecryptionException e) {
|
||||
logger.debug(() -> new ParameterizedMessage("Failed to decrypt SAML EncryptedID [{}] with [{}]",
|
||||
text(encrypted, 512), describe(getSpConfiguration().getEncryptionCredential().getEntityCertificate())), e);
|
||||
throw samlException("Failed to decrypt SAML EncryptedID " + text(encrypted, 32), e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getSessionIndex(LogoutRequest logoutRequest) {
|
||||
return logoutRequest.getSessionIndexes()
|
||||
.stream()
|
||||
.map(as -> as.getSessionIndex())
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private void checkDestination(LogoutRequest request) {
|
||||
final String url = getSpConfiguration().getLogoutUrl();
|
||||
if (url == null) {
|
||||
throw samlException("SAML request " + request.getID() + " is for destination " + request.getDestination()
|
||||
+ " but this realm is not configured for logout");
|
||||
}
|
||||
if (url.equals(request.getDestination()) == false) {
|
||||
throw samlException("SAML request " + request.getID() + " is for destination " + request.getDestination()
|
||||
+ " but this realm uses " + url);
|
||||
}
|
||||
}
|
||||
|
||||
static class ParsedQueryString {
|
||||
final String samlRequest;
|
||||
final boolean hasSignature;
|
||||
final String relayState;
|
||||
|
||||
ParsedQueryString(String samlRequest, boolean hasSignature, String relayState) {
|
||||
this.samlRequest = samlRequest;
|
||||
this.hasSignature = hasSignature;
|
||||
this.relayState = relayState;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
private final String requestId;
|
||||
private final SamlNameId nameId;
|
||||
private final String session;
|
||||
private final String relayState;
|
||||
|
||||
public Result(String requestId, SamlNameId nameId, String session, String relayState) {
|
||||
this.requestId = requestId;
|
||||
this.nameId = nameId;
|
||||
this.session = session;
|
||||
this.relayState = relayState;
|
||||
}
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public SamlNameId getNameId() {
|
||||
return nameId;
|
||||
}
|
||||
|
||||
public String getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public String getRelayState() {
|
||||
return relayState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SamlLogoutRequestHandler.Result{" +
|
||||
"requestId='" + requestId + '\'' +
|
||||
", nameId=" + nameId +
|
||||
", session='" + session + '\'' +
|
||||
", relayState='" + relayState + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||
import org.opensaml.saml.saml2.core.Issuer;
|
||||
import org.opensaml.saml.saml2.core.LogoutRequest;
|
||||
import org.opensaml.saml.saml2.core.NameID;
|
||||
import org.opensaml.saml.saml2.core.SessionIndex;
|
||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.SSODescriptor;
|
||||
|
||||
/**
|
||||
* Constructs {@code <LogoutRequest<} objects for use in a SAML Single-Sign-Out flow.
|
||||
*/
|
||||
class SamlLogoutRequestMessageBuilder extends SamlMessageBuilder {
|
||||
private final NameID nameId;
|
||||
private final String session;
|
||||
|
||||
SamlLogoutRequestMessageBuilder(Clock clock, SpConfiguration serviceProvider, EntityDescriptor identityProvider,
|
||||
NameID nameId, String session) {
|
||||
super(identityProvider, serviceProvider, clock);
|
||||
this.nameId = nameId;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
LogoutRequest build() {
|
||||
final String logoutUrl = getLogoutUrl();
|
||||
if (Strings.isNullOrEmpty(logoutUrl)) {
|
||||
logger.debug("Cannot perform logout because the IDP {} does not provide a logout service", identityProvider.getEntityID());
|
||||
return null;
|
||||
}
|
||||
|
||||
final SessionIndex sessionIndex = SamlUtils.buildObject(SessionIndex.class, SessionIndex.DEFAULT_ELEMENT_NAME);
|
||||
sessionIndex.setSessionIndex(session);
|
||||
|
||||
final Issuer issuer = buildIssuer();
|
||||
|
||||
final LogoutRequest request = SamlUtils.buildObject(LogoutRequest.class, LogoutRequest.DEFAULT_ELEMENT_NAME);
|
||||
request.setID(buildId());
|
||||
request.setIssueInstant(now());
|
||||
request.setDestination(logoutUrl);
|
||||
request.setNameID(nameId);
|
||||
request.getSessionIndexes().add(sessionIndex);
|
||||
request.setIssuer(issuer);
|
||||
return request;
|
||||
}
|
||||
|
||||
protected String getLogoutUrl() {
|
||||
return getIdentityProviderEndpoint(SAMLConstants.SAML2_REDIRECT_BINDING_URI, SSODescriptor::getSingleLogoutServices);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||
import org.opensaml.saml.saml2.core.LogoutResponse;
|
||||
import org.opensaml.saml.saml2.core.Status;
|
||||
import org.opensaml.saml.saml2.core.StatusCode;
|
||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.SSODescriptor;
|
||||
|
||||
import static org.elasticsearch.xpack.security.authc.saml.SamlUtils.samlException;
|
||||
|
||||
/**
|
||||
* Constructs {@code <LogoutRespond<} objects for use in a SAML Single-Sign-Out flow.
|
||||
*/
|
||||
class SamlLogoutResponseBuilder extends SamlMessageBuilder {
|
||||
private final String inResponseTo;
|
||||
private final String statusValue;
|
||||
|
||||
SamlLogoutResponseBuilder(Clock clock, SpConfiguration serviceProvider, EntityDescriptor identityProvider,
|
||||
String inResponseTo, String statusValue) {
|
||||
super(identityProvider, serviceProvider, clock);
|
||||
this.inResponseTo = inResponseTo;
|
||||
this.statusValue = statusValue;
|
||||
}
|
||||
|
||||
LogoutResponse build() {
|
||||
final String destination = getLogoutUrl();
|
||||
if (Strings.isNullOrEmpty(destination)) {
|
||||
throw samlException("Cannot send LogoutResponse because the IDP {} does not provide a logout service",
|
||||
identityProvider.getEntityID());
|
||||
}
|
||||
|
||||
final LogoutResponse res = SamlUtils.buildObject(LogoutResponse.class, LogoutResponse.DEFAULT_ELEMENT_NAME);
|
||||
res.setID(buildId());
|
||||
res.setIssueInstant(now());
|
||||
res.setDestination(destination);
|
||||
res.setIssuer(buildIssuer());
|
||||
res.setInResponseTo(inResponseTo);
|
||||
|
||||
final Status status = SamlUtils.buildObject(Status.class, Status.DEFAULT_ELEMENT_NAME);
|
||||
final StatusCode statusCode= SamlUtils.buildObject(StatusCode.class, StatusCode.DEFAULT_ELEMENT_NAME);
|
||||
statusCode.setValue(this.statusValue);
|
||||
status.setStatusCode(statusCode);
|
||||
res.setStatus(status);
|
||||
return res;
|
||||
}
|
||||
|
||||
protected String getLogoutUrl() {
|
||||
return getIdentityProviderEndpoint(SAMLConstants.SAML2_REDIRECT_BINDING_URI, SSODescriptor::getSingleLogoutServices);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.opensaml.saml.saml2.core.Issuer;
|
||||
import org.opensaml.saml.saml2.metadata.Endpoint;
|
||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
|
||||
|
||||
/**
|
||||
* Abstract base class for object that build some sort of {@link org.opensaml.saml.common.SAMLObject}
|
||||
*/
|
||||
public abstract class SamlMessageBuilder {
|
||||
|
||||
protected final Logger logger;
|
||||
protected final Clock clock;
|
||||
protected final SpConfiguration serviceProvider;
|
||||
protected final EntityDescriptor identityProvider;
|
||||
|
||||
public SamlMessageBuilder(EntityDescriptor identityProvider, SpConfiguration serviceProvider, Clock clock) {
|
||||
this.logger = Loggers.getLogger(getClass());
|
||||
this.identityProvider = identityProvider;
|
||||
this.serviceProvider = serviceProvider;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
protected String getIdentityProviderEndpoint(String binding,
|
||||
Function<IDPSSODescriptor, ? extends Collection<? extends Endpoint>> selector) {
|
||||
final List<String> locations = identityProvider.getRoleDescriptors(IDPSSODescriptor.DEFAULT_ELEMENT_NAME).stream()
|
||||
.map(rd -> (IDPSSODescriptor) rd)
|
||||
.flatMap(idp -> selector.apply(idp).stream())
|
||||
.filter(endp -> binding.equals(endp.getBinding()))
|
||||
.map(sso -> sso.getLocation())
|
||||
.collect(Collectors.toList());
|
||||
if (locations.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (locations.size() > 1) {
|
||||
throw new ElasticsearchException("Found multiple locations for binding [{}] in descriptor [{}] - [{}]",
|
||||
binding, identityProvider.getID(), locations);
|
||||
}
|
||||
return locations.get(0);
|
||||
}
|
||||
|
||||
protected DateTime now() {
|
||||
return new DateTime(clock.millis(), DateTimeZone.UTC);
|
||||
}
|
||||
|
||||
protected Issuer buildIssuer() {
|
||||
Issuer issuer = SamlUtils.buildObject(Issuer.class, Issuer.DEFAULT_ELEMENT_NAME);
|
||||
issuer.setValue(this.serviceProvider.getEntityId());
|
||||
return issuer;
|
||||
}
|
||||
|
||||
protected String buildId() {
|
||||
// 20 bytes (160 bits) of randomness as recommended by the SAML spec
|
||||
return SamlUtils.generateSecureNCName(20);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.elasticsearch.cli.EnvironmentAwareCommand;
|
||||
import org.elasticsearch.cli.ExitCodes;
|
||||
import org.elasticsearch.cli.SuppressForbidden;
|
||||
import org.elasticsearch.cli.Terminal;
|
||||
import org.elasticsearch.cli.UserException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.logging.ServerLoggers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.LocaleUtils;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.xpack.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.security.authc.RealmSettings;
|
||||
import org.elasticsearch.xpack.security.authc.saml.SamlSpMetadataBuilder.ContactInfo;
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.impl.EntityDescriptorMarshaller;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import static org.elasticsearch.xpack.security.authc.RealmSettings.getRealmType;
|
||||
import static org.elasticsearch.xpack.security.authc.saml.SamlRealm.require;
|
||||
|
||||
/**
|
||||
* CLI tool to generate SAML Metadata for a Service Provider (realm)
|
||||
*/
|
||||
public class SamlMetadataCommand extends EnvironmentAwareCommand {
|
||||
|
||||
static final String METADATA_SCHEMA = "saml-schema-metadata-2.0.xsd";
|
||||
|
||||
private final OptionSpec<String> outputPathSpec;
|
||||
private final OptionSpec<Void> batchSpec;
|
||||
private final OptionSpec<String> realmSpec;
|
||||
private final OptionSpec<String> localeSpec;
|
||||
private final OptionSpec<String> serviceNameSpec;
|
||||
private final OptionSpec<String> attributeSpec;
|
||||
private final OptionSpec<String> orgNameSpec;
|
||||
private final OptionSpec<String> orgDisplayNameSpec;
|
||||
private final OptionSpec<String> orgUrlSpec;
|
||||
private final OptionSpec<Void> contactsSpec;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new SamlMetadataCommand().main(args, Terminal.DEFAULT);
|
||||
}
|
||||
|
||||
public SamlMetadataCommand() {
|
||||
super("Generate Service Provider Metadata for a SAML realm");
|
||||
outputPathSpec = parser.accepts("out", "path of the xml file that should be generated").withRequiredArg();
|
||||
batchSpec = parser.accepts("batch", "Do not prompt");
|
||||
realmSpec = parser.accepts("realm", "name of the elasticsearch realm for which metadata should be generated").withRequiredArg();
|
||||
localeSpec = parser.accepts("locale", "the locale to be used for elements that require a language").withRequiredArg();
|
||||
serviceNameSpec = parser.accepts("service-name", "the name to apply to the attribute consuming service").withRequiredArg();
|
||||
attributeSpec = parser.accepts("attribute", "additional SAML attributes to request").withRequiredArg();
|
||||
orgNameSpec = parser.accepts("organisation-name", "the name of the organisation operating this service").withRequiredArg();
|
||||
orgDisplayNameSpec = parser.accepts("organisation-display-name", "the display-name of the organisation operating this service")
|
||||
.availableIf(orgNameSpec).withRequiredArg();
|
||||
orgUrlSpec = parser.accepts("organisation-url", "the URL of the organisation operating this service")
|
||||
.requiredIf(orgNameSpec).withRequiredArg();
|
||||
contactsSpec = parser.accepts("contacts", "Include contact information in metadata").availableUnless(batchSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(Terminal terminal, OptionSet options, Environment env) throws Exception {
|
||||
// OpenSAML prints a lot of _stuff_ at info level, that really isn't needed in a command line tool.
|
||||
ServerLoggers.setLevel(Loggers.getLogger("org.opensaml"), Level.WARN);
|
||||
|
||||
final Logger logger = Loggers.getLogger(getClass());
|
||||
SamlUtils.initialize(logger);
|
||||
|
||||
final EntityDescriptor descriptor = buildEntityDescriptor(terminal, options, env);
|
||||
final Path xml = writeOutput(terminal, options, descriptor);
|
||||
validateXml(terminal, xml);
|
||||
}
|
||||
|
||||
// package-protected for testing
|
||||
EntityDescriptor buildEntityDescriptor(Terminal terminal, OptionSet options, Environment env) throws Exception {
|
||||
final boolean batch = options.has(batchSpec);
|
||||
|
||||
final RealmConfig realm = findRealm(terminal, options, env);
|
||||
terminal.println(Terminal.Verbosity.VERBOSE,
|
||||
"Using realm configuration\n=====\n" + realm.settings().toDelimitedString('\n') + "=====");
|
||||
final Locale locale = findLocale(options);
|
||||
terminal.println(Terminal.Verbosity.VERBOSE, "Using locale: " + locale.toLanguageTag());
|
||||
|
||||
final SpConfiguration spConfig = SamlRealm.getSpConfiguration(realm);
|
||||
final SamlSpMetadataBuilder builder = new SamlSpMetadataBuilder(locale, spConfig.getEntityId())
|
||||
.assertionConsumerServiceUrl(spConfig.getAscUrl())
|
||||
.singleLogoutServiceUrl(spConfig.getLogoutUrl())
|
||||
.encryptionCredential(spConfig.getEncryptionCredential())
|
||||
.signingCredential(spConfig.getSigningConfiguration().getCredential())
|
||||
.authnRequestsSigned(spConfig.getSigningConfiguration().shouldSign(AuthnRequest.DEFAULT_ELEMENT_LOCAL_NAME))
|
||||
.nameIdFormat(require(realm, SamlRealmSettings.NAMEID_FORMAT))
|
||||
.serviceName(option(serviceNameSpec, options, env.settings().get("cluster.name")));
|
||||
|
||||
Map<String, String> attributes = getAttributeNames(options, realm);
|
||||
for (String attr : attributes.keySet()) {
|
||||
final String name;
|
||||
String friendlyName;
|
||||
final String settingName = attributes.get(attr);
|
||||
final String attributeSource = settingName == null ? "command line" : '"' + settingName + '"';
|
||||
if (attr.contains(":")) {
|
||||
name = attr;
|
||||
if (batch) {
|
||||
friendlyName = settingName;
|
||||
} else {
|
||||
friendlyName = terminal.readText("What is the friendly name for " +
|
||||
attributeSource
|
||||
+ " attribute \"" + attr + "\" [default: " +
|
||||
(settingName == null ? "none" : settingName) +
|
||||
"] ");
|
||||
if (Strings.isNullOrEmpty(friendlyName)) {
|
||||
friendlyName = settingName;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (batch) {
|
||||
throw new UserException(ExitCodes.CONFIG, "Option " + batchSpec.toString() + " is specified, but attribute "
|
||||
+ attr + " appears to be a FriendlyName value");
|
||||
}
|
||||
friendlyName = attr;
|
||||
name = requireText(terminal,
|
||||
"What is the standard (urn) name for " + attributeSource + " attribute \"" + attr + "\" (required): ");
|
||||
}
|
||||
terminal.println(Terminal.Verbosity.VERBOSE, "Requesting attribute '" + name + "' (FriendlyName: '" + friendlyName + "')");
|
||||
builder.withAttribute(friendlyName, name);
|
||||
}
|
||||
|
||||
if (options.has(orgNameSpec) && options.has(orgUrlSpec)) {
|
||||
String name = orgNameSpec.value(options);
|
||||
builder.organization(name, option(orgDisplayNameSpec, options, name), orgUrlSpec.value(options));
|
||||
}
|
||||
|
||||
if (options.has(contactsSpec)) {
|
||||
terminal.println("\nPlease enter the personal details for each contact to be included in the metadata");
|
||||
do {
|
||||
final String givenName = requireText(terminal, "What is the given name for the contact: ");
|
||||
final String surName = requireText(terminal, "What is the surname for the contact: ");
|
||||
final String displayName = givenName + ' ' + surName;
|
||||
final String email = requireText(terminal, "What is the email address for " + displayName + ": ");
|
||||
String type;
|
||||
while (true) {
|
||||
type = requireText(terminal, "What is the contact type for " + displayName + ": ");
|
||||
if (ContactInfo.TYPES.containsKey(type)) {
|
||||
break;
|
||||
} else {
|
||||
terminal.println("Type '" + type + "' is not valid. Valid values are "
|
||||
+ Strings.collectionToCommaDelimitedString(ContactInfo.TYPES.keySet()));
|
||||
}
|
||||
}
|
||||
builder.withContact(type, givenName, surName, email);
|
||||
} while (terminal.promptYesNo("Enter details for another contact", true));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private Path writeOutput(Terminal terminal, OptionSet options, EntityDescriptor descriptor) throws Exception {
|
||||
final EntityDescriptorMarshaller marshaller = new EntityDescriptorMarshaller();
|
||||
final Element element = marshaller.marshall(descriptor);
|
||||
final Path outputFile = resolvePath(option(outputPathSpec, options, "saml-elasticsearch-metadata.xml"));
|
||||
final Writer writer = Files.newBufferedWriter(outputFile);
|
||||
SamlUtils.print(element, writer, true);
|
||||
terminal.println("\nWrote SAML metadata to " + outputFile);
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
|
||||
private void validateXml(Terminal terminal, Path xml) throws Exception {
|
||||
try (InputStream xmlInput = Files.newInputStream(xml)) {
|
||||
SamlUtils.validate(xmlInput, METADATA_SCHEMA);
|
||||
terminal.println(Terminal.Verbosity.VERBOSE, "The generated metadata file conforms to the SAML metadata schema");
|
||||
} catch (SAXException e) {
|
||||
terminal.println(Terminal.Verbosity.SILENT, "Error - The generated metadata file does not conform to the SAML metadata schema");
|
||||
terminal.println("While validating " + xml.toString() + " the follow errors were found:");
|
||||
printExceptions(terminal, e);
|
||||
throw new UserException(ExitCodes.CODE_ERROR, "Generated metadata is not valid");
|
||||
}
|
||||
}
|
||||
|
||||
private void printExceptions(Terminal terminal, Throwable throwable) {
|
||||
terminal.println(" - " + throwable.getMessage());
|
||||
for (Throwable sup : throwable.getSuppressed()) {
|
||||
printExceptions(terminal, sup);
|
||||
}
|
||||
if (throwable.getCause() != null && throwable.getCause() != throwable) {
|
||||
printExceptions(terminal, throwable.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "CLI tool working from current directory")
|
||||
private Path resolvePath(String name) {
|
||||
return PathUtils.get(name).normalize();
|
||||
}
|
||||
|
||||
private String requireText(Terminal terminal, String prompt) {
|
||||
String value = null;
|
||||
while (Strings.isNullOrEmpty(value)) {
|
||||
value = terminal.readText(prompt);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private <T> T option(OptionSpec<T> spec, OptionSet options, T defaultValue) {
|
||||
if (options.has(spec)) {
|
||||
return spec.value(options);
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of saml-attribute name to configuration-setting name
|
||||
*/
|
||||
private Map<String, String> getAttributeNames(OptionSet options, RealmConfig realm) {
|
||||
Map<String, String> attributes = new LinkedHashMap<>();
|
||||
for (String a : attributeSpec.values(options)) {
|
||||
attributes.put(a, null);
|
||||
}
|
||||
final Settings attributeSettings = realm.settings().getByPrefix(SamlRealmSettings.AttributeSetting.ATTRIBUTES_PREFIX);
|
||||
for (String key : sorted(attributeSettings.keySet())) {
|
||||
final String attr = attributeSettings.get(key);
|
||||
attributes.put(attr, key);
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
// We sort this Set so that it is deterministic for testing
|
||||
private SortedSet<String> sorted(Set<String> strings) {
|
||||
return new TreeSet<>(strings);
|
||||
}
|
||||
|
||||
private RealmConfig findRealm(Terminal terminal, OptionSet options, Environment env) throws UserException {
|
||||
final Map<String, Settings> realms = RealmSettings.getRealmSettings(env.settings());
|
||||
if (options.has(realmSpec)) {
|
||||
final String name = realmSpec.value(options);
|
||||
final Settings settings = realms.get(name);
|
||||
if (settings == null) {
|
||||
throw new UserException(ExitCodes.CONFIG, "No such realm '" + name + "' defined in " + env.configFile());
|
||||
}
|
||||
final String realmType = getRealmType(settings);
|
||||
if (isSamlRealm(realmType)) {
|
||||
return buildRealm(name, settings, env);
|
||||
} else {
|
||||
throw new UserException(ExitCodes.CONFIG, "Realm '" + name + "' is not a SAML realm (is '" + realmType + "')");
|
||||
}
|
||||
} else {
|
||||
final List<Map.Entry<String, Settings>> saml = realms.entrySet().stream()
|
||||
.filter(entry -> isSamlRealm(getRealmType(entry.getValue())))
|
||||
.collect(Collectors.toList());
|
||||
if (saml.isEmpty()) {
|
||||
throw new UserException(ExitCodes.CONFIG, "There is no SAML realm configured in " + env.configFile());
|
||||
}
|
||||
if (saml.size() > 1) {
|
||||
terminal.println("Using configuration in " + env.configFile());
|
||||
terminal.println("Found multiple SAML realms: " + saml.stream().map(Map.Entry::getKey).collect(Collectors.joining(", ")));
|
||||
terminal.println("Use the -" + optionName(realmSpec) + " option to specify an explicit realm");
|
||||
throw new UserException(ExitCodes.CONFIG,
|
||||
"Found multiple SAML realms, please specify one with '-" + optionName(realmSpec) + "'");
|
||||
}
|
||||
final Map.Entry<String, Settings> entry = saml.get(0);
|
||||
terminal.println("Building metadata for SAML realm " + entry.getKey());
|
||||
return buildRealm(entry.getKey(), entry.getValue(), env);
|
||||
}
|
||||
}
|
||||
|
||||
private String optionName(OptionSpec<?> spec) {
|
||||
return spec.options().get(0);
|
||||
}
|
||||
|
||||
private RealmConfig buildRealm(String name, Settings settings, Environment env) {
|
||||
return new RealmConfig(name, settings, env.settings(), env, new ThreadContext(env.settings()));
|
||||
}
|
||||
|
||||
private boolean isSamlRealm(String realmType) {
|
||||
return SamlRealmSettings.TYPE.equals(realmType);
|
||||
}
|
||||
|
||||
private Locale findLocale(OptionSet options) {
|
||||
if (options.has(localeSpec)) {
|
||||
return LocaleUtils.parse(localeSpec.value(options));
|
||||
} else {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// For testing
|
||||
OptionParser getParser() {
|
||||
return parser;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.authc.saml;
|
||||
|
||||
import org.opensaml.saml.saml2.core.NameID;
|
||||
import org.opensaml.saml.saml2.core.Subject;
|
||||
|
||||
/**
|
||||
* Lightweight (non-XML) representation of a SAML {@code NameID} element
|
||||
*/
|
||||
public class SamlNameId {
|
||||
final String format;
|
||||
final String value;
|
||||
final String idpNameQualifier;
|
||||
final String spNameQualifier;
|
||||
final String spProvidedId;
|
||||
|
||||
public SamlNameId(String format, String value, String idpNameQualifier, String spNameQualifier, String spProvidedId) {
|
||||
this.format = format;
|
||||
this.value = value;
|
||||
this.idpNameQualifier = idpNameQualifier;
|
||||
this.spNameQualifier = spNameQualifier;
|
||||
this.spProvidedId = spProvidedId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NameId(" + format + ")=" + value;
|
||||
}
|
||||
|
||||
public NameID asXml() {
|
||||
final NameID nameId = SamlUtils.buildObject(NameID.class, NameID.DEFAULT_ELEMENT_NAME);
|
||||
nameId.setFormat(format);
|
||||
nameId.setValue(value);
|
||||
nameId.setNameQualifier(idpNameQualifier);
|
||||
nameId.setSPNameQualifier(spNameQualifier);
|
||||
nameId.setSPProvidedID(spProvidedId);
|
||||
return nameId;
|
||||
}
|
||||
|
||||
static SamlNameId fromXml(NameID name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
return new SamlNameId(name.getFormat(), name.getValue(), name.getNameQualifier(),
|
||||
name.getSPNameQualifier(), name.getSPProvidedID());
|
||||
}
|
||||
|
||||
static SamlNameId forSubject(Subject subject) {
|
||||
if (subject == null) {
|
||||
return null;
|
||||
}
|
||||
final NameID name = subject.getNameID();
|
||||
return fromXml(name);
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue