Delegated authorization using Microsoft Graph (SDK) (#128396)

* Delegated authorization using Microsoft Graph (SDK)
---------

Co-authored-by: elasticsearchmachine <infra-root+elasticsearchmachine@elastic.co>
Co-authored-by: Johannes Freden Jansson <johannes.freden@elastic.co>
Co-authored-by: Johannes Fredén <109296772+jfreden@users.noreply.github.com>
This commit is contained in:
Richard Dennehy 2025-06-12 10:03:32 +01:00 committed by GitHub
parent f02a3c423f
commit 63da93d4c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 3865 additions and 205 deletions

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.gradle.internal.dependencies.patches.hdfs;
package org.elasticsearch.gradle.internal.dependencies.patches;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@ -16,7 +16,7 @@ public class MethodReplacement extends MethodVisitor {
private final MethodVisitor delegate;
private final Runnable bodyWriter;
MethodReplacement(MethodVisitor delegate, Runnable bodyWriter) {
public MethodReplacement(MethodVisitor delegate, Runnable bodyWriter) {
super(Opcodes.ASM9);
this.delegate = delegate;
this.bodyWriter = bodyWriter;

View file

@ -0,0 +1,61 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.gradle.internal.dependencies.patches.azurecore;
import org.elasticsearch.gradle.internal.dependencies.patches.PatcherInfo;
import org.elasticsearch.gradle.internal.dependencies.patches.Utils;
import org.gradle.api.artifacts.transform.CacheableTransform;
import org.gradle.api.artifacts.transform.InputArtifact;
import org.gradle.api.artifacts.transform.TransformAction;
import org.gradle.api.artifacts.transform.TransformOutputs;
import org.gradle.api.artifacts.transform.TransformParameters;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Classpath;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.List;
import java.util.regex.Pattern;
import static org.elasticsearch.gradle.internal.dependencies.patches.PatcherInfo.classPatcher;
@CacheableTransform
public abstract class AzureCoreClassPatcher implements TransformAction<TransformParameters.None> {
private static final String JAR_FILE_TO_PATCH = "azure-core-[\\d.]*\\.jar";
private static final List<PatcherInfo> CLASS_PATCHERS = List.of(
classPatcher(
"com/azure/core/implementation/ImplUtils.class",
"7beda5bdff5ea460cfc08721a188cf07d16e0c987dae45401fca7abf4e6e6c0e",
ImplUtilsPatcher::new
)
);
@Classpath
@InputArtifact
public abstract Provider<FileSystemLocation> getInputArtifact();
@Override
public void transform(@NotNull TransformOutputs outputs) {
File inputFile = getInputArtifact().get().getAsFile();
if (Pattern.matches(JAR_FILE_TO_PATCH, inputFile.getName())) {
System.out.println("Patching " + inputFile.getName());
File outputFile = outputs.file(inputFile.getName().replace(".jar", "-patched.jar"));
Utils.patchJar(inputFile, outputFile, CLASS_PATCHERS, true);
} else {
System.out.println("Skipping " + inputFile.getName());
outputs.file(getInputArtifact());
}
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.gradle.internal.dependencies.patches.azurecore;
import org.elasticsearch.gradle.internal.dependencies.patches.MethodReplacement;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
class ImplUtilsPatcher extends ClassVisitor {
ImplUtilsPatcher(ClassVisitor classVisitor) {
super(Opcodes.ASM9, classVisitor);
}
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
// `addShutdownHook` invokes `java.lang.Runtime.addShutdownHook`, which is forbidden (i.e. it will throw an Entitlements error).
// We replace the method body here with `return null`.
if (name.equals("addShutdownHookSafely")) {
return new MethodReplacement(mv, () -> {
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitInsn(Opcodes.ARETURN);
});
}
return mv;
}
}

View file

@ -9,6 +9,7 @@
package org.elasticsearch.gradle.internal.dependencies.patches.hdfs;
import org.elasticsearch.gradle.internal.dependencies.patches.MethodReplacement;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

View file

@ -9,6 +9,7 @@
package org.elasticsearch.gradle.internal.dependencies.patches.hdfs;
import org.elasticsearch.gradle.internal.dependencies.patches.MethodReplacement;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

View file

@ -117,6 +117,7 @@ dependencies {
log4jConfig project(path: ":distribution", configuration: 'log4jConfig')
tini "krallin:tini:0.19.0:${tiniArch}"
allPlugins project(path: ':plugins', configuration: 'allPlugins')
allPlugins project(path: ':x-pack:extras:plugins', configuration: 'allPlugins')
filebeat_aarch64 "beats:filebeat:${VersionProperties.elasticsearch}:linux-arm64@tar.gz"
filebeat_x86_64 "beats:filebeat:${VersionProperties.elasticsearch}:linux-x86_64@tar.gz"
filebeat_fips_aarch64 "beats:filebeat-fips:${VersionProperties.elasticsearch}:linux-arm64@tar.gz"

View file

@ -0,0 +1,5 @@
pr: 128396
summary: Delegated authorization using Microsoft Graph (SDK)
area: Authorization
type: feature
issues: []

View file

@ -96,21 +96,41 @@
<sha256 value="9f39244d3cc201a6fd69eb5800d9951624bcac548aee3b51efd642e4acbf457c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.azure" name="azure-core" version="1.55.3">
<artifact name="azure-core-1.55.3.jar">
<sha256 value="53f64121176aca98b8634bd79fddabeea26c0ebcc092e342631a2b0d2dcec9e5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.azure" name="azure-core-http-netty" version="1.15.3">
<artifact name="azure-core-http-netty-1.15.3.jar">
<sha256 value="18e9932c0aa3f42cee80fccee1907849fa9db5be40b8cffa21d1a7fc1d7457e5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.azure" name="azure-core-http-okhttp" version="1.12.10">
<artifact name="azure-core-http-okhttp-1.12.10.jar">
<sha256 value="e4c1b1ca50aa4f4aad83a110f2d3edca637d2cd378265e17d7101b12dcfafa01" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.azure" name="azure-identity" version="1.13.2">
<artifact name="azure-identity-1.13.2.jar">
<sha256 value="f00ebc38b1dcf75a35129b8b0ad434514c84fd310c7afaf12824bd04e590f243" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.azure" name="azure-identity" version="1.15.4">
<artifact name="azure-identity-1.15.4.jar">
<sha256 value="3e7c4ca616570ad2a5886528c909d6888feb0fd8db637f19dadbc3f987cae2c0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.azure" name="azure-json" version="1.2.0">
<artifact name="azure-json-1.2.0.jar">
<sha256 value="c7419ae04f668eb4dc3864cd64a6b77207abd93b9ee784f042dea7cb452840de" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.azure" name="azure-json" version="1.5.0">
<artifact name="azure-json-1.5.0.jar">
<sha256 value="65b1ec85f5d734221f1028d60c95bf5b453515797d6ab68ea8c36a6f2d5bc56b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.azure" name="azure-storage-blob" version="12.27.1">
<artifact name="azure-storage-blob-12.27.1.jar">
<sha256 value="31915426834400cac854f48441c168d55aa6fc054527f28f1d242a7067affd14" origin="Generated by Gradle"/>
@ -136,6 +156,11 @@
<sha256 value="1006208324a08dadd5463ad2edfa3057a1ec3adee9d79c3ca00b14ef2f66cbce" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.azure" name="azure-xml" version="1.2.0">
<artifact name="azure-xml-1.2.0.jar">
<sha256 value="69d9559c561d3125bfd2bf9b5248601e442902bc755d935dde3edba97dc0d931" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.bettercloud" name="vault-java-driver" version="4.1.0">
<artifact name="vault-java-driver-4.1.0.jar">
<sha256 value="8878c47594fcfaa3e5b6fd75bb0e8f82cd8d4e7d947dd2a8e986f6d1f4b6c23e" origin="Generated by Gradle"/>
@ -983,11 +1008,61 @@
<sha256 value="23478ea37253045c58b8b80367a89b48cbf2bd3e3ef43c2ddd3e7d96b853ef43" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.azure" name="msal4j" version="1.19.1">
<artifact name="msal4j-1.19.1.jar">
<sha256 value="5c8bbf40c72d259230c7717348fc026dcaf2a6cb7f029eb78abc2d0235e66e0f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.azure" name="msal4j-persistence-extension" version="1.3.0">
<artifact name="msal4j-persistence-extension-1.3.0.jar">
<sha256 value="dfc41c817fbfa76057af6ffe4379dbca6a5e16b8e87df8bdda23f371756c2d09" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.graph" name="microsoft-graph" version="6.36.0">
<artifact name="microsoft-graph-6.36.0.jar">
<sha256 value="c5f2e55fb61e009e33ad082738b9b3fb5c6e4feb5afb8d8ea204d8c8f3159b19" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.graph" name="microsoft-graph-core" version="3.6.1">
<artifact name="microsoft-graph-core-3.6.1.jar">
<sha256 value="9a25de0892a84e14f1fd55540fad10ec4a391d9d0e60921d696842bfb55ffcf6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.kiota" name="microsoft-kiota-abstractions" version="1.8.4">
<artifact name="microsoft-kiota-abstractions-1.8.4.jar">
<sha256 value="356b8f931a10c374cfc542ac9b01b300a8459fce19e409535897876c8a70f639" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.kiota" name="microsoft-kiota-authentication-azure" version="1.8.4">
<artifact name="microsoft-kiota-authentication-azure-1.8.4.jar">
<sha256 value="99ace59180a36bb3a2345a16d859e708804fb6720b2814ac14c31c191abfd32b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.kiota" name="microsoft-kiota-http-okHttp" version="1.8.4">
<artifact name="microsoft-kiota-http-okHttp-1.8.4.jar">
<sha256 value="0d8fa07c3f0a9f1d1e72446aab2d4675914039ea972f9792a0d551487323e81e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.kiota" name="microsoft-kiota-serialization-form" version="1.8.4">
<artifact name="microsoft-kiota-serialization-form-1.8.4.jar">
<sha256 value="3e6eb722a145770cec9417f4254e6b9887f95bdecb61733adfce540b6e7c64a0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.kiota" name="microsoft-kiota-serialization-json" version="1.8.4">
<artifact name="microsoft-kiota-serialization-json-1.8.4.jar">
<sha256 value="38700fbd1864a5fc983b4971174bff3d2aec96f793ebf244d045f14bcd41c5d5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.kiota" name="microsoft-kiota-serialization-multipart" version="1.8.4">
<artifact name="microsoft-kiota-serialization-multipart-1.8.4.jar">
<sha256 value="9a50b202f4b3b5f5a904c50eb71a0f5b0d473d7fd8143389ca014f9636a47ec8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.kiota" name="microsoft-kiota-serialization-text" version="1.8.4">
<artifact name="microsoft-kiota-serialization-text-1.8.4.jar">
<sha256 value="406c6b596f3fd1c0fd34f844ba0823d1035fdea984adc04245c0cf1daed02346" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.microsoft.sqlserver" name="mssql-jdbc" version="6.2.1.jre7">
<artifact name="mssql-jdbc-6.2.1.jre7.jar">
<sha256 value="9cfa259450ae3471d2e6e2c3d8aefcce236e3daef8b3734d21dc93c3a5bbe806" origin="Generated by Gradle"/>
@ -1078,6 +1153,11 @@
<sha256 value="88ac9fd1bb51f82bcc664cc1eb9c225c90dc4389d660231b4cc737bebfe7d0aa" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okhttp3" name="okhttp" version="4.11.0">
<artifact name="okhttp-4.11.0.jar">
<sha256 value="ee8f6bd6cd1257013d748330f4ca147638a9fbcb52fb388d5ac93cf53408745d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okhttp3" name="logging-interceptor" version="4.12.0">
<artifact name="logging-interceptor-4.12.0.jar">
<sha256 value="f3e8d5f0903c250c2b55d2f47fcfe008e80634385da8385161c7a63aaed0c74c" origin="Generated by Gradle"/>
@ -1098,6 +1178,16 @@
<sha256 value="114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okio" name="okio-jvm" version="3.2.0">
<artifact name="okio-jvm-3.2.0.jar">
<sha256 value="b642baef4c570055de4cb3d1667b2b16dced901ff8066345a063691aa06025a4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okio" name="okio-jvm" version="3.4.0">
<artifact name="okio-jvm-3.4.0.jar">
<sha256 value="0139ec7a506dbbd54cad62291b019cb850534be097c8c66c1000d5fbe8edef3e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.squareup.okio" name="okio-jvm" version="3.6.0">
<artifact name="okio-jvm-3.6.0.jar">
<sha256 value="67543f0736fc422ae927ed0e504b98bc5e269fda0d3500579337cb713da28412" origin="Generated by Gradle"/>
@ -1448,6 +1538,11 @@
<sha256 value="626cce8b732f65e3fad4111bea84376c423c7fddffd1c85f8ad8f214a18acde6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.github.std-uritemplate" name="std-uritemplate" version="2.0.0">
<artifact name="std-uritemplate-2.0.0.jar">
<sha256 value="626bbacb7e2ca4c2e8427133c39788d38e7f85d0ace0c697255afc1a8f46be6e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.grpc" name="grpc-api" version="1.70.0">
<artifact name="grpc-api-1.70.0.jar">
<sha256 value="45faf2ac1bf2791e8fdabce53684a86b62c99b84cba26fb13a5ba3f4abf80d6c" origin="Generated by Gradle"/>
@ -1583,6 +1678,11 @@
<sha256 value="6566f1f1133d611ff4e8b8fdb8eb18577b970425620315363ee9be43843b14bf" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.opentelemetry" name="opentelemetry-api" version="1.50.0">
<artifact name="opentelemetry-api-1.50.0.jar">
<sha256 value="2eaaac5f268b135f0e11dd30637d71df5751a0bb7ed6268659be57104d63122b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.opentelemetry" name="opentelemetry-context" version="1.31.0">
<artifact name="opentelemetry-context-1.31.0.jar">
<sha256 value="664896a5c34bcda20c95c8f45198a95e8f97a1cd5e5c2923978f42dddada787d" origin="Generated by Gradle"/>
@ -1593,6 +1693,11 @@
<sha256 value="15b4fc4234e6dca6d54800d572694ecbd07ba52c15fc5b221b4da5517ce8d90d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.opentelemetry" name="opentelemetry-context" version="1.50.0">
<artifact name="opentelemetry-context-1.50.0.jar">
<sha256 value="76f9dfe1a6f74d5081e07bde1f7cb9a06879d317ec0ae0f61dd8fb2be9afad4f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.opentelemetry" name="opentelemetry-sdk" version="1.47.0">
<artifact name="opentelemetry-sdk-1.47.0.jar">
<sha256 value="4a09eb2ee484769973e14218a34e6da54f35955aa02b26dc5238b0c2ed6a801d" origin="Generated by Gradle"/>
@ -1613,6 +1718,11 @@
<sha256 value="a9255f60d92e8fc58c3c87320cc439936e08227db65bd88eb97e844af853e608" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.projectreactor" name="reactor-core" version="3.7.5">
<artifact name="reactor-core-3.7.5.jar">
<sha256 value="0d69b6e28f045ae454f903d2d97cde44d7b57f6c7b8c8c5ef369649bcce7b3ce" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.projectreactor.netty" name="reactor-netty-core" version="1.0.45">
<artifact name="reactor-netty-core-1.0.45.jar">
<sha256 value="b38fdbc0618bc193359bb32807bc17b691fed42259dd27aa02b18ddbcf2b0844" origin="Generated by Gradle"/>
@ -4006,6 +4116,11 @@
<sha256 value="edc8e3ec9796a5f41c1ae44b2d318507ee6ac1212f121d93d33699b3d0aff638" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib" version="1.6.20">
<artifact name="kotlin-stdlib-1.6.20.jar">
<sha256 value="eeb51c2b67b26233fd81d0bc4f8044ec849718890905763ceffd84a31e2cb799" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib" version="1.9.10">
<artifact name="kotlin-stdlib-1.9.10.jar">
<sha256 value="55e989c512b80907799f854309f3bc7782c5b3d13932442d0379d5c472711504" origin="Generated by Gradle"/>

View file

@ -1,19 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import org.elasticsearch.xpack.security.authz.microsoft.MicrosoftGraphAuthzPlugin;
module org.elasticsearch.plugin.security.authz {
requires org.elasticsearch.base;
requires org.elasticsearch.server;
requires org.elasticsearch.xcore;
requires org.elasticsearch.logging;
provides org.elasticsearch.xpack.core.security.SecurityExtension with MicrosoftGraphAuthzPlugin;
}

View file

@ -1,50 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.xpack.security.authz.microsoft;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger;
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
import org.elasticsearch.xpack.core.security.authc.Realm;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.user.User;
public class MicrosoftGraphAuthzRealm extends Realm {
private static final Logger logger = LogManager.getLogger(MicrosoftGraphAuthzRealm.class);
public MicrosoftGraphAuthzRealm(RealmConfig config) {
super(config);
}
@Override
public boolean supports(AuthenticationToken token) {
return false;
}
@Override
public AuthenticationToken token(ThreadContext context) {
return null;
}
@Override
public void authenticate(AuthenticationToken token, ActionListener<AuthenticationResult<User>> listener) {
listener.onResponse(AuthenticationResult.notHandled());
}
@Override
public void lookupUser(String username, ActionListener<User> listener) {
logger.info("Microsoft Graph Authz not yet implemented, returning empty roles for [{}]", username);
listener.onResponse(new User(username));
}
}

View file

@ -1,24 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.xpack.security.authz.microsoft;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import java.util.ArrayList;
import java.util.List;
public class MicrosoftGraphAuthzRealmSettings {
public static final String REALM_TYPE = "microsoft_graph";
public static List<Setting<?>> getSettings() {
return new ArrayList<>(RealmSettings.getStandardSettings(REALM_TYPE));
}
}

View file

@ -155,6 +155,7 @@ addSubProjects('', new File(rootProject.projectDir, 'qa'))
addSubProjects('test', new File(rootProject.projectDir, 'test/external-modules'))
addSubProjects('', new File(rootProject.projectDir, 'x-pack'))
addSubProjects('', new File(rootProject.projectDir, 'x-pack/libs'))
addSubProjects('', new File(rootProject.projectDir, 'x-pack/extras/plugins'))
include projects.toArray(new String[0])
@ -172,4 +173,4 @@ if (extraProjects.exists()) {
}
}
include 'qa:vector'
include 'qa:vector'

View file

@ -6,16 +6,3 @@
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
apply plugin: "elasticsearch.internal-java-rest-test"
esplugin {
name = "microsoft-graph-authz"
description = "Microsoft Graph Delegated Authorization Realm Plugin"
classname = "org.elasticsearch.xpack.security.authz.microsoft.MicrosoftGraphAuthzPlugin"
extendedPlugins = ["x-pack-security"]
}
dependencies {
compileOnly project(":x-pack:plugin:core")
}

View file

@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
configurations {
allPlugins
}
// only configure immediate children of plugins dir
configure(subprojects.findAll { it.parent.path == project.path }) {
group = 'org.elasticsearch.plugin'
apply plugin: 'elasticsearch.internal-es-plugin'
esplugin {
// for local ES plugins, the name of the plugin is the same as the directory
name = project.name
licenseFile = layout.settingsDirectory.file('licenses/ELASTIC-LICENSE-2.0.txt').asFile
noticeFile = layout.settingsDirectory.file('NOTICE.txt').asFile
}
parent.artifacts.add('allPlugins', tasks.named('bundlePlugin'))
}

View file

@ -0,0 +1,194 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
plugins {
id "elasticsearch.internal-java-rest-test"
}
esplugin {
name = "microsoft-graph-authz"
description = "Microsoft Graph Delegated Authorization Realm Plugin"
classname = "org.elasticsearch.xpack.security.authz.microsoft.MicrosoftGraphAuthzPlugin"
extendedPlugins = ["x-pack-security"]
}
def patched = Attribute.of('patched', Boolean)
configurations {
compileClasspath {
attributes {
attribute(patched, true)
}
}
runtimeClasspath {
attributes {
attribute(patched, true)
}
}
testCompileClasspath {
attributes {
attribute(patched, true)
}
}
testRuntimeClasspath {
attributes {
attribute(patched, true)
}
}
}
dependencies {
compileOnly project(":x-pack:plugin:core")
implementation "com.microsoft.graph:microsoft-graph:6.36.0"
implementation "com.microsoft.graph:microsoft-graph-core:3.6.1"
implementation project(path: "kiota-merged", configuration: 'shadow')
implementation "com.azure:azure-identity:1.15.4"
implementation "com.azure:azure-core:1.55.3"
implementation "com.azure:azure-json:1.5.0"
implementation "com.azure:azure-xml:1.2.0"
implementation "com.fasterxml.jackson.core:jackson-core:${versions.jackson}"
implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}"
implementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${versions.jackson}"
implementation "org.reactivestreams:reactive-streams:1.0.4"
implementation "io.projectreactor:reactor-core:3.7.5"
compileOnly "org.slf4j:slf4j-api:${versions.slf4j}"
runtimeOnly "com.microsoft.azure:msal4j:1.19.1"
runtimeOnly "com.microsoft.azure:msal4j-persistence-extension:1.3.0"
runtimeOnly "net.java.dev.jna:jna:${versions.jna}"
runtimeOnly "net.java.dev.jna:jna-platform:${versions.jna}"
runtimeOnly "io.opentelemetry:opentelemetry-api:1.50.0"
runtimeOnly "io.opentelemetry:opentelemetry-context:1.50.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.6.20"
implementation "com.squareup.okhttp3:okhttp:4.11.0"
runtimeOnly "com.squareup.okio:okio:3.4.0"
runtimeOnly "com.squareup.okio:okio-jvm:3.4.0"
runtimeOnly "io.github.std-uritemplate:std-uritemplate:2.0.0"
implementation "com.azure:azure-core-http-okhttp:1.12.10"
implementation "com.google.code.gson:gson:2.10"
testRuntimeOnly "net.minidev:json-smart:2.5.2"
testRuntimeOnly "com.nimbusds:oauth2-oidc-sdk:11.22.2"
testRuntimeOnly "com.nimbusds:content-type:2.3"
testImplementation testArtifact(project(":x-pack:plugin:core"))
attributesSchema {
attribute(patched)
}
artifactTypes.getByName("jar") {
attributes.attribute(patched, false)
}
registerTransform(org.elasticsearch.gradle.internal.dependencies.patches.azurecore.AzureCoreClassPatcher) {
from.attribute(patched, false)
to.attribute(patched, true)
}
}
tasks.named("javadoc").configure { enabled = false }
tasks.named("dependencyLicenses").configure {
mapping from: "microsoft-graph-core", to: "microsoft-graph"
mapping from: /azure-.*/, to: "azure"
mapping from: /jackson.*/, to: "jackson"
mapping from: /kotlin.*/, to: "kotlin"
mapping from: /msal4j.*/, to: "msal4j"
mapping from: /jna.*/, to: "jna"
mapping from: /opentelemetry.*/, to: "opentelemetry"
mapping from: /okio.*/, to: "okio"
}
tasks.named("thirdPartyAudit").configure {
ignoreViolations(
'reactor.core.publisher.CallSiteSupplierFactory$SharedSecretsCallSiteSupplierFactory',
'reactor.core.publisher.CallSiteSupplierFactory$SharedSecretsCallSiteSupplierFactory$TracingException'
)
ignoreMissingClasses(
'android.net.http.X509TrustManagerExtensions',
'android.net.ssl.SSLSockets',
'android.os.Build$VERSION',
'android.security.NetworkSecurityPolicy',
'android.util.Log',
'com.auth0.jwk.Jwk',
'com.auth0.jwk.JwkProvider',
'com.nimbusds.common.contenttype.ContentType',
'com.nimbusds.jose.JWSAlgorithm',
'com.nimbusds.jose.JWSHeader$Builder',
'com.nimbusds.jose.util.Base64URL',
'com.nimbusds.jose.util.StandardCharset',
'com.nimbusds.jwt.JWT',
'com.nimbusds.jwt.JWTClaimsSet',
'com.nimbusds.jwt.JWTClaimsSet$Builder',
'com.nimbusds.jwt.JWTParser',
'com.nimbusds.jwt.SignedJWT',
'com.nimbusds.oauth2.sdk.AuthorizationGrant',
'com.nimbusds.oauth2.sdk.GrantType',
'com.nimbusds.oauth2.sdk.ParseException',
'com.nimbusds.oauth2.sdk.ResourceOwnerPasswordCredentialsGrant',
'com.nimbusds.oauth2.sdk.SAML2BearerGrant',
'com.nimbusds.oauth2.sdk.auth.ClientAuthentication',
'com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod',
'com.nimbusds.oauth2.sdk.auth.JWTAuthenticationClaimsSet',
'com.nimbusds.oauth2.sdk.auth.PrivateKeyJWT',
'com.nimbusds.oauth2.sdk.auth.Secret',
'com.nimbusds.oauth2.sdk.http.HTTPRequest',
'com.nimbusds.oauth2.sdk.http.HTTPRequest$Method',
'com.nimbusds.oauth2.sdk.http.HTTPResponse',
'com.nimbusds.oauth2.sdk.id.ClientID',
'com.nimbusds.oauth2.sdk.token.AccessToken',
'com.nimbusds.oauth2.sdk.token.RefreshToken',
'com.nimbusds.oauth2.sdk.util.JSONObjectUtils',
'com.nimbusds.oauth2.sdk.util.URLUtils',
'com.nimbusds.openid.connect.sdk.OIDCTokenResponse',
'com.nimbusds.openid.connect.sdk.token.OIDCTokens',
'io.jsonwebtoken.Claims',
'io.jsonwebtoken.JweHeader',
'io.jsonwebtoken.Jws',
'io.jsonwebtoken.JwsHeader',
'io.jsonwebtoken.JwtParser',
'io.jsonwebtoken.JwtParserBuilder',
'io.jsonwebtoken.Jwts',
'io.jsonwebtoken.LocatorAdapter',
'io.micrometer.context.ContextAccessor',
'io.micrometer.context.ContextRegistry',
'io.micrometer.context.ContextSnapshot',
'io.micrometer.context.ContextSnapshot$Scope',
'io.micrometer.context.ContextSnapshotFactory',
'io.micrometer.context.ContextSnapshotFactory$Builder',
'io.micrometer.context.ThreadLocalAccessor',
'io.micrometer.core.instrument.Clock',
'io.micrometer.core.instrument.Counter',
'io.micrometer.core.instrument.Counter$Builder',
'io.micrometer.core.instrument.DistributionSummary',
'io.micrometer.core.instrument.DistributionSummary$Builder',
'io.micrometer.core.instrument.Meter',
'io.micrometer.core.instrument.MeterRegistry',
'io.micrometer.core.instrument.Metrics',
'io.micrometer.core.instrument.Tag',
'io.micrometer.core.instrument.Tags',
'io.micrometer.core.instrument.Timer',
'io.micrometer.core.instrument.Timer$Builder',
'io.micrometer.core.instrument.Timer$Sample',
'io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics',
'io.micrometer.core.instrument.composite.CompositeMeterRegistry',
'io.micrometer.core.instrument.search.Search',
'kotlin.io.path.PathsKt',
'net.minidev.json.JSONObject',
'org.bouncycastle.jsse.BCSSLParameters',
'org.bouncycastle.jsse.BCSSLSocket',
'org.conscrypt.Conscrypt',
'org.conscrypt.Conscrypt$Version',
'org.conscrypt.ConscryptHostnameVerifier',
'org.openjsse.javax.net.ssl.SSLParameters',
'org.openjsse.javax.net.ssl.SSLSocket',
'reactor.blockhound.BlockHound$Builder',
'reactor.blockhound.integration.BlockHoundIntegration'
)
}

View file

@ -0,0 +1,75 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
apply plugin: 'elasticsearch.build'
apply plugin: 'com.gradleup.shadow'
// because each of these declares a module `com.microsoft.kiota`, the plugin will crash at runtime; we work around this by building a shadow "uber jar"
// so that there is only one `com.microsoft.kiota` module
dependencies {
implementation "com.microsoft.kiota:microsoft-kiota-abstractions:1.8.4"
implementation "com.microsoft.kiota:microsoft-kiota-authentication-azure:1.8.4"
implementation "com.microsoft.kiota:microsoft-kiota-http-okHttp:1.8.4"
implementation "com.microsoft.kiota:microsoft-kiota-serialization-json:1.8.4"
implementation "com.microsoft.kiota:microsoft-kiota-serialization-text:1.8.4"
implementation "com.microsoft.kiota:microsoft-kiota-serialization-form:1.8.4"
implementation "com.microsoft.kiota:microsoft-kiota-serialization-multipart:1.8.4"
}
tasks.named('shadowJar').configure {
manifest {
attributes 'Automatic-Module-Name': 'com.microsoft.kiota'
}
}
tasks.named("dependencyLicenses").configure {
mapping from: /microsoft-kiota.*/, to: "kiota"
}
tasks.named("thirdPartyAudit").configure {
ignoreMissingClasses(
'com.azure.core.credential.AccessToken',
'com.azure.core.credential.TokenCredential',
'com.azure.core.credential.TokenRequestContext',
'com.google.gson.JsonArray',
'com.google.gson.JsonElement',
'com.google.gson.JsonObject',
'com.google.gson.JsonParser',
'com.google.gson.JsonPrimitive',
'com.google.gson.stream.JsonWriter',
'io.github.stduritemplate.StdUriTemplate',
'io.opentelemetry.api.GlobalOpenTelemetry',
'io.opentelemetry.api.common.AttributeKey',
'io.opentelemetry.api.trace.Span',
'io.opentelemetry.api.trace.SpanBuilder',
'io.opentelemetry.api.trace.StatusCode',
'io.opentelemetry.api.trace.Tracer',
'io.opentelemetry.context.Context',
'io.opentelemetry.context.Scope',
'kotlin.Pair',
'okhttp3.Call',
'okhttp3.Call$Factory',
'okhttp3.Headers',
'okhttp3.HttpUrl',
'okhttp3.HttpUrl$Builder',
'okhttp3.Interceptor',
'okhttp3.Interceptor$Chain',
'okhttp3.MediaType',
'okhttp3.OkHttpClient$Builder',
'okhttp3.Protocol',
'okhttp3.Request',
'okhttp3.Request$Builder',
'okhttp3.RequestBody',
'okhttp3.Response',
'okhttp3.Response$Builder',
'okhttp3.ResponseBody',
'okio.BufferedSink',
'okio.Okio'
)
}

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
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

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Microsoft
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.

View file

@ -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.

View file

@ -0,0 +1,8 @@
This copy of Jackson JSON processor streaming parser/generator is licensed under the
Apache (Software) License, version 2.0 ("the License").
See the License for details about distribution rights, and the
specific rights regarding derivate works.
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0

View file

@ -0,0 +1,20 @@
# Jackson JSON processor
Jackson is a high-performance, Free/Open Source JSON processing library.
It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has
been in development since 2007.
It is currently developed by a community of developers, as well as supported
commercially by FasterXML.com.
## Licensing
Jackson core and extension components may licensed under different licenses.
To find the details that apply to this artifact see the accompanying LICENSE file.
For more information, including possible other licensing options, contact
FasterXML.com (http://fasterxml.com).
## Credits
A list of contributors may be found from CREDITS file, which is included
in some artifacts (usually source distributions); but is always available
from the source code management (SCM) system project uses.

View file

@ -0,0 +1,177 @@
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

View file

@ -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.

View file

@ -0,0 +1,8 @@
=========================================================================
== NOTICE file corresponding to the section 4 d of ==
== the Apache License, Version 2.0, ==
== in this case for the Kotlin Compiler distribution. ==
=========================================================================
Kotlin Compiler
Copyright 2010-2024 JetBrains s.r.o and respective authors and developers

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Microsoft Graph
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.

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation. 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

View file

@ -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.

View file

@ -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.

View file

@ -0,0 +1,201 @@
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.

View file

@ -0,0 +1,7 @@
MIT No Attribution
Copyright 2014 Reactive Streams
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.
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.

View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
https://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
https://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.

View file

@ -0,0 +1,201 @@
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 2023 std-uritemplate
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.

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import org.elasticsearch.xpack.security.authz.microsoft.MicrosoftGraphAuthzPlugin;
module org.elasticsearch.plugin.security.authz {
requires org.elasticsearch.base;
requires org.elasticsearch.server;
requires org.elasticsearch.xcore;
requires org.elasticsearch.logging;
requires org.apache.httpcomponents.httpclient;
requires org.apache.httpcomponents.httpcore;
requires com.microsoft.kiota;
requires com.microsoft.graph;
requires com.azure.identity;
requires com.microsoft.graph.core;
requires kotlin.stdlib;
requires com.google.gson;
requires okhttp3;
requires com.azure.core.http.okhttp;
requires org.apache.logging.log4j;
provides org.elasticsearch.xpack.core.security.SecurityExtension with MicrosoftGraphAuthzPlugin;
}

View file

@ -1,10 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.security.authz.microsoft;
@ -20,7 +18,10 @@ import java.util.Map;
public class MicrosoftGraphAuthzPlugin extends Plugin implements SecurityExtension {
@Override
public Map<String, Realm.Factory> getRealms(SecurityComponents components) {
return Map.of(MicrosoftGraphAuthzRealmSettings.REALM_TYPE, MicrosoftGraphAuthzRealm::new);
return Map.of(
MicrosoftGraphAuthzRealmSettings.REALM_TYPE,
config -> new MicrosoftGraphAuthzRealm(components.roleMapper(), config, components.threadPool())
);
}
@Override

View file

@ -0,0 +1,235 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.security.authz.microsoft;
import okhttp3.OkHttpClient;
import com.azure.core.http.okhttp.OkHttpAsyncHttpClientBuilder;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.core.requests.BaseGraphRequestAdapter;
import com.microsoft.graph.core.tasks.PageIterator;
import com.microsoft.graph.models.Group;
import com.microsoft.graph.models.GroupCollectionResponse;
import com.microsoft.graph.serviceclient.GraphServiceClient;
import com.microsoft.kiota.authentication.AzureIdentityAuthenticationProvider;
import com.microsoft.kiota.http.middleware.RetryHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.license.License;
import org.elasticsearch.license.LicenseUtils;
import org.elasticsearch.license.LicensedFeature;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
import org.elasticsearch.xpack.core.security.authc.Realm;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper;
import org.elasticsearch.xpack.core.security.support.CancellableRunnable;
import org.elasticsearch.xpack.core.security.user.User;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MicrosoftGraphAuthzRealm extends Realm {
private static final Logger logger = LogManager.getLogger(MicrosoftGraphAuthzRealm.class);
private static final int PAGE_SIZE = 999;
private static final boolean DISABLE_INSTANCE_DISCOVERY = System.getProperty(
"tests.azure.credentials.disable_instance_discovery",
"false"
).equals("true");
static final LicensedFeature.Momentary MICROSOFT_GRAPH_FEATURE = LicensedFeature.momentary(
"security-realms",
"microsoft_graph",
License.OperationMode.PLATINUM
);
private final RealmConfig config;
private final UserRoleMapper roleMapper;
private final GraphServiceClient client;
private final XPackLicenseState licenseState;
private final ThreadPool threadPool;
private final TimeValue executionTimeout;
public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config, ThreadPool threadPool) {
this(roleMapper, config, buildClient(config), XPackPlugin.getSharedLicenseState(), threadPool);
}
// for testing
MicrosoftGraphAuthzRealm(
UserRoleMapper roleMapper,
RealmConfig config,
GraphServiceClient client,
XPackLicenseState licenseState,
ThreadPool threadPool
) {
super(config);
validate(config);
this.config = config;
this.roleMapper = roleMapper;
this.client = client;
this.licenseState = licenseState;
this.threadPool = threadPool;
this.executionTimeout = config.getSetting(MicrosoftGraphAuthzRealmSettings.EXECUTION_TIMEOUT);
}
private static void validate(RealmConfig config) {
require(config, MicrosoftGraphAuthzRealmSettings.CLIENT_ID);
require(config, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET);
require(config, MicrosoftGraphAuthzRealmSettings.TENANT_ID);
}
private static <T extends CharSequence> void require(RealmConfig config, Setting.AffixSetting<T> setting) {
final var value = config.getSetting(setting);
if (value.isEmpty()) {
throw new SettingsException("The configuration setting [" + RealmSettings.getFullSettingKey(config, setting) + "] is required");
}
}
@Override
public boolean supports(AuthenticationToken token) {
return false;
}
@Override
public AuthenticationToken token(ThreadContext context) {
return null;
}
@Override
public void authenticate(AuthenticationToken token, ActionListener<AuthenticationResult<User>> listener) {
listener.onResponse(AuthenticationResult.notHandled());
}
@Override
public void lookupUser(String principal, ActionListener<User> listener) {
if (MICROSOFT_GRAPH_FEATURE.check(licenseState) == false) {
listener.onFailure(LicenseUtils.newComplianceException(MICROSOFT_GRAPH_FEATURE.getName()));
return;
}
final var runnable = new CancellableRunnable<>(listener, ex -> null, () -> doLookupUser(principal, listener), logger);
threadPool.generic().execute(runnable);
threadPool.schedule(runnable::maybeTimeout, executionTimeout, EsExecutors.DIRECT_EXECUTOR_SERVICE);
}
private void doLookupUser(String principal, ActionListener<User> listener) {
try {
final var userProperties = fetchUserProperties(client, principal);
final var groups = fetchGroupMembership(client, principal);
final var userData = new UserRoleMapper.UserData(principal, null, groups, Map.of(), config);
roleMapper.resolveRoles(userData, listener.delegateFailureAndWrap((l, roles) -> {
final var user = new User(
principal,
roles.toArray(Strings.EMPTY_ARRAY),
userProperties.v1(),
userProperties.v2(),
Map.of(),
true
);
logger.trace("Authorized user from Microsoft Graph {}", user);
l.onResponse(user);
}));
} catch (Exception e) {
logger.error(Strings.format("Failed to authorize [%s] with MS Graph realm", principal), e);
listener.onFailure(e);
}
}
private static GraphServiceClient buildClient(RealmConfig config) {
final var clientSecret = config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET);
final var timeout = config.getSetting(MicrosoftGraphAuthzRealmSettings.HTTP_REQUEST_TIMEOUT);
final var httpClient = new OkHttpClient.Builder().callTimeout(Duration.ofSeconds(timeout.seconds()))
.addInterceptor(new RetryHandler())
.build();
final var credentialProviderBuilder = new ClientSecretCredentialBuilder().clientId(
config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_ID)
)
.clientSecret(clientSecret.toString())
.tenantId(config.getSetting(MicrosoftGraphAuthzRealmSettings.TENANT_ID))
.authorityHost(config.getSetting(MicrosoftGraphAuthzRealmSettings.ACCESS_TOKEN_HOST))
.httpClient(new OkHttpAsyncHttpClientBuilder(httpClient).build())
.enableUnsafeSupportLogging()
.enableAccountIdentifierLogging();
if (DISABLE_INSTANCE_DISCOVERY) {
credentialProviderBuilder.disableInstanceDiscovery();
}
final var credentialProvider = credentialProviderBuilder.build();
return new GraphServiceClient(
new BaseGraphRequestAdapter(
new AzureIdentityAuthenticationProvider(credentialProvider, Strings.EMPTY_ARRAY, "https://graph.microsoft.com/.default"),
config.getSetting(MicrosoftGraphAuthzRealmSettings.API_HOST),
httpClient
)
);
}
private Tuple<String, String> fetchUserProperties(GraphServiceClient client, String userId) {
var response = client.users()
.byUserId(userId)
.get(requestConfig -> requestConfig.queryParameters.select = new String[] { "displayName", "mail" });
logger.trace("Fetched user with name [{}] and email [{}] from Microsoft Graph", response.getDisplayName(), response.getMail());
return Tuple.tuple(response.getDisplayName(), response.getMail());
}
private List<String> fetchGroupMembership(GraphServiceClient client, String userId) throws ReflectiveOperationException {
List<String> groups = new ArrayList<>();
var groupMembership = client.users().byUserId(userId).transitiveMemberOf().graphGroup().get(requestConfig -> {
requestConfig.queryParameters.select = new String[] { "id" };
requestConfig.queryParameters.top = PAGE_SIZE;
});
var pageIterator = new PageIterator.Builder<Group, GroupCollectionResponse>().client(client)
.collectionPage(groupMembership)
.collectionPageFactory(GroupCollectionResponse::createFromDiscriminatorValue)
.requestConfigurator(requestInfo -> {
requestInfo.addQueryParameter("%24select", new String[] { "id" });
requestInfo.addQueryParameter("%24top", String.valueOf(PAGE_SIZE));
return requestInfo;
})
.processPageItemCallback(group -> {
groups.add(group.getId());
return true;
})
.build();
pageIterator.iterate();
if (logger.isTraceEnabled()) {
logger.trace("Fetched [{}] groups from Microsoft Graph: [{}]", groups.size(), String.join(", ", groups));
}
return groups;
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.security.authz.microsoft;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import java.util.ArrayList;
import java.util.List;
public class MicrosoftGraphAuthzRealmSettings {
public static final String REALM_TYPE = "microsoft_graph";
public static final Setting.AffixSetting<String> CLIENT_ID = RealmSettings.simpleString(
REALM_TYPE,
"client_id",
Setting.Property.NodeScope
);
public static final Setting.AffixSetting<SecureString> CLIENT_SECRET = RealmSettings.secureString(REALM_TYPE, "client_secret");
public static final Setting.AffixSetting<String> TENANT_ID = RealmSettings.simpleString(
REALM_TYPE,
"tenant_id",
Setting.Property.NodeScope
);
public static final Setting.AffixSetting<String> ACCESS_TOKEN_HOST = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(REALM_TYPE),
"access_token_host",
key -> Setting.simpleString(key, "https://login.microsoftonline.com", Setting.Property.NodeScope)
);
public static final Setting.AffixSetting<String> API_HOST = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(REALM_TYPE),
"graph_host",
key -> Setting.simpleString(key, "https://graph.microsoft.com/v1.0", Setting.Property.NodeScope)
);
public static final Setting.AffixSetting<TimeValue> HTTP_REQUEST_TIMEOUT = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(REALM_TYPE),
"http_request_timeout",
key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(10), Setting.Property.NodeScope)
);
public static final Setting.AffixSetting<TimeValue> EXECUTION_TIMEOUT = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(REALM_TYPE),
"execution_timeout",
key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(30), Setting.Property.NodeScope)
);
public static List<Setting<?>> getSettings() {
var settings = new ArrayList<Setting<?>>(RealmSettings.getStandardSettings(REALM_TYPE));
settings.add(CLIENT_ID);
settings.add(CLIENT_SECRET);
settings.add(TENANT_ID);
settings.add(ACCESS_TOKEN_HOST);
settings.add(API_HOST);
settings.add(HTTP_REQUEST_TIMEOUT);
settings.add(EXECUTION_TIMEOUT);
return settings;
}
}

View file

@ -0,0 +1,5 @@
okhttp3:
- outbound_network
- manage_threads
okio:
- manage_threads

View file

@ -0,0 +1,508 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.security.authz.microsoft;
import com.microsoft.graph.models.Group;
import com.microsoft.graph.models.GroupCollectionResponse;
import com.microsoft.graph.models.odataerrors.MainError;
import com.microsoft.graph.models.odataerrors.ODataError;
import com.microsoft.graph.serviceclient.GraphServiceClient;
import com.microsoft.graph.users.UsersRequestBuilder;
import com.microsoft.graph.users.item.UserItemRequestBuilder;
import com.microsoft.graph.users.item.transitivememberof.TransitiveMemberOfRequestBuilder;
import com.microsoft.graph.users.item.transitivememberof.graphgroup.GraphGroupRequestBuilder;
import com.microsoft.kiota.RequestAdapter;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.license.MockLicenseState;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.MockLog;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper;
import org.elasticsearch.xpack.core.security.user.User;
import org.junit.After;
import org.junit.Before;
import java.util.Arrays;
import java.util.Set;
import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey;
import static org.elasticsearch.xpack.security.authz.microsoft.MicrosoftGraphAuthzRealm.MICROSOFT_GRAPH_FEATURE;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class MicrosoftGraphAuthzRealmTests extends ESTestCase {
private final Settings globalSettings = Settings.builder().put("path.home", createTempDir()).build();
private final Environment env = TestEnvironment.newEnvironment(globalSettings);
private final ThreadContext threadContext = new ThreadContext(globalSettings);
private final ThreadPool threadPool = new TestThreadPool(getClass().getName());
private final String realmName = randomAlphaOfLengthBetween(4, 10);
private final String roleName = randomAlphaOfLengthBetween(4, 10);
private final String username = randomAlphaOfLengthBetween(4, 10);
private final String name = randomAlphaOfLengthBetween(4, 10);
private final String email = Strings.format("%s@example.com", randomAlphaOfLengthBetween(4, 10));
private final String groupId = randomAlphaOfLengthBetween(4, 10);
private final RealmConfig.RealmIdentifier realmId = new RealmConfig.RealmIdentifier(
MicrosoftGraphAuthzRealmSettings.REALM_TYPE,
realmName
);
private final String clientId = randomAlphaOfLengthBetween(4, 10);
private final String clientSecret = randomAlphaOfLengthBetween(4, 10);
private final String tenantId = randomAlphaOfLengthBetween(4, 10);
private static final AuthenticationToken fakeToken = new AuthenticationToken() {
@Override
public String principal() {
fail("Should never be called");
return null;
}
@Override
public Object credentials() {
fail("Should never be called");
return null;
}
@Override
public void clearCredentials() {
fail("Should never be called");
}
};
@Before
public void setUp() throws Exception {
super.setUp();
final var logger = LogManager.getLogger(MicrosoftGraphAuthzRealm.class);
Loggers.setLevel(logger, Level.TRACE);
}
@After
public void tearDown() throws Exception {
super.tearDown();
terminate(threadPool);
}
public void testLookupUser() {
try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) {
mockLog.addExpectation(
new MockLog.SeenEventExpectation(
"Fetch user properties",
MicrosoftGraphAuthzRealm.class.getName(),
Level.TRACE,
Strings.format("Fetched user with name [%s] and email [%s] from Microsoft Graph", name, email)
)
);
mockLog.addExpectation(
new MockLog.SeenEventExpectation(
"Fetch group membership",
MicrosoftGraphAuthzRealm.class.getName(),
Level.TRACE,
Strings.format("Fetched [1] groups from Microsoft Graph: [%s]", groupId)
)
);
final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName));
final var realmSettings = realmSettings().build();
final var config = new RealmConfig(realmId, realmSettings, env, threadContext);
final var client = mock(GraphServiceClient.class);
when(client.getRequestAdapter()).thenReturn(mock(RequestAdapter.class));
final var userRequestBuilder = mockGetUser(client);
when(userRequestBuilder.get(any())).thenReturn(user(name, email));
final var graphGroupRequestBuilder = mockGetGroupMembership(userRequestBuilder);
when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership(groupId));
final var licenseState = mockLicense(true);
final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool);
final var future = new PlainActionFuture<User>();
realm.lookupUser(username, future);
final var user = future.actionGet();
assertThat(user.principal(), equalTo(username));
assertThat(user.fullName(), equalTo(name));
assertThat(user.email(), equalTo(email));
assertThat(user.roles(), arrayContaining(roleName));
mockLog.assertAllExpectationsMatched();
}
}
public void testHandleGetUserPropertiesError() {
final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName));
final var realmSettings = realmSettings().build();
final var config = new RealmConfig(realmId, realmSettings, env, threadContext);
final var client = mock(GraphServiceClient.class);
final var requestAdapter = mock(RequestAdapter.class);
when(client.getRequestAdapter()).thenReturn(requestAdapter);
final var userItemRequestBuilder = mockGetUser(client);
when(userItemRequestBuilder.get(any())).thenThrow(graphError("bad stuff happened"));
final var licenseState = mockLicense(true);
final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool);
final var future = new PlainActionFuture<User>();
try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) {
mockLog.addExpectation(
new MockLog.SeenEventExpectation(
"Log exception",
MicrosoftGraphAuthzRealm.class.getName(),
Level.ERROR,
Strings.format("Failed to authorize [%s] with MS Graph realm", username)
)
);
realm.lookupUser(username, future);
final var thrown = assertThrows(ODataError.class, future::actionGet);
assertThat(thrown.getMessage(), equalTo("bad stuff happened"));
mockLog.assertAllExpectationsMatched();
}
}
public void testHandleGetGroupMembershipError() {
final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName));
final var realmSettings = realmSettings().build();
final var config = new RealmConfig(realmId, realmSettings, env, threadContext);
final var client = mock(GraphServiceClient.class);
when(client.getRequestAdapter()).thenReturn(mock(RequestAdapter.class));
final var userRequestBuilder = mockGetUser(client);
when(userRequestBuilder.get(any())).thenReturn(user(name, email));
final var graphGroupRequestBuilder = mockGetGroupMembership(userRequestBuilder);
when(graphGroupRequestBuilder.get(any())).thenThrow(graphError("bad stuff happened"));
final var licenseState = mockLicense(true);
final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool);
final var future = new PlainActionFuture<User>();
try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) {
mockLog.addExpectation(
new MockLog.SeenEventExpectation(
"Log exception",
MicrosoftGraphAuthzRealm.class.getName(),
Level.ERROR,
Strings.format("Failed to authorize [%s] with MS Graph realm", username)
)
);
realm.lookupUser(username, future);
final var thrown = assertThrows(ODataError.class, future::actionGet);
assertThat(thrown.getMessage(), equalTo("bad stuff happened"));
mockLog.assertAllExpectationsMatched();
}
}
public void testGroupMembershipPagination() {
final var groupId2 = randomAlphaOfLengthBetween(4, 10);
final var groupId3 = randomAlphaOfLengthBetween(4, 10);
final var roleMapper = mockRoleMapper(Set.of(groupId, groupId2, groupId3), Set.of(roleName));
final var realmSettings = realmSettings().build();
final var config = new RealmConfig(realmId, realmSettings, env, threadContext);
final var client = mock(GraphServiceClient.class);
final var requestAdapter = mock(RequestAdapter.class);
when(client.getRequestAdapter()).thenReturn(requestAdapter);
final var userItemRequestBuilder = mockGetUser(client);
when(userItemRequestBuilder.get(any())).thenReturn(user(name, email));
final var groupMembership1 = groupMembership(groupId);
groupMembership1.setOdataNextLink("http://localhost:12345/page2");
final var groupMembership2 = groupMembership(groupId2);
groupMembership2.setOdataNextLink("http://localhost:12345/page3");
final var groupMembership3 = groupMembership(groupId3);
final var graphGroupRequestBuilder = mockGetGroupMembership(userItemRequestBuilder);
when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership1);
when(requestAdapter.send(any(), any(), any())).thenReturn(groupMembership2, groupMembership3);
final var licenseState = mockLicense(true);
final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool);
final var future = new PlainActionFuture<User>();
realm.lookupUser(username, future);
final var user = future.actionGet();
assertThat(user.principal(), equalTo(username));
assertThat(user.fullName(), equalTo(name));
assertThat(user.email(), equalTo(email));
assertThat(user.roles(), arrayContaining(roleName));
}
public void testLicenseCheck() {
final var roleMapper = mock(UserRoleMapper.class);
final var realmSettings = realmSettings().build();
final var config = new RealmConfig(realmId, realmSettings, env, threadContext);
final var client = mock(GraphServiceClient.class);
final var licenseState = mockLicense(false);
final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool);
final var future = new PlainActionFuture<User>();
realm.lookupUser(username, future);
final var thrown = assertThrows(ElasticsearchSecurityException.class, future::actionGet);
assertThat(thrown.getMessage(), equalTo("current license is non-compliant for [microsoft_graph]"));
}
public void testClientIdSettingRequired() {
final var roleMapper = mock(UserRoleMapper.class);
final var realmSettings = realmSettings().put(
getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_ID),
randomBoolean() ? "" : null
).build();
final var config = new RealmConfig(realmId, realmSettings, env, threadContext);
final var client = mock(GraphServiceClient.class);
final var licenseState = mockLicense(true);
final var thrown = assertThrows(
SettingsException.class,
() -> new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool)
);
assertThat(
thrown.getMessage(),
equalTo(
Strings.format(
"The configuration setting [%s] is required",
getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_ID)
)
)
);
}
public void testClientSecretSettingRequired() {
final var roleMapper = mock(UserRoleMapper.class);
final var secureSettings = new MockSecureSettings();
if (randomBoolean()) {
secureSettings.setString(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET), "");
}
final var realmSettings = Settings.builder()
.put(globalSettings)
.put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0)
.put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_ID), clientId)
.put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.TENANT_ID), tenantId)
.setSecureSettings(secureSettings)
.build();
final var config = new RealmConfig(realmId, realmSettings, env, threadContext);
final var client = mock(GraphServiceClient.class);
final var licenseState = mockLicense(true);
final var thrown = assertThrows(
SettingsException.class,
() -> new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool)
);
assertThat(
thrown.getMessage(),
equalTo(
Strings.format(
"The configuration setting [%s] is required",
getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET)
)
)
);
}
public void testTenantIdSettingRequired() {
final var roleMapper = mock(UserRoleMapper.class);
final var realmSettings = realmSettings().put(
getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.TENANT_ID),
randomBoolean() ? "" : null
).build();
final var config = new RealmConfig(realmId, realmSettings, env, threadContext);
final var client = mock(GraphServiceClient.class);
final var licenseState = mockLicense(true);
final var thrown = assertThrows(
SettingsException.class,
() -> new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool)
);
assertThat(
thrown.getMessage(),
equalTo(
Strings.format(
"The configuration setting [%s] is required",
getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.TENANT_ID)
)
)
);
}
public void testSupportsAlwaysReturnsFalse() {
final var roleMapper = mock(UserRoleMapper.class);
final var realmSettings = realmSettings().build();
final var config = new RealmConfig(realmId, realmSettings, env, threadContext);
final var client = mock(GraphServiceClient.class);
final var licenseState = mockLicense(true);
final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool);
assertThat(realm.supports(fakeToken), equalTo(false));
}
public void testTokenAlwaysReturnsNull() {
final var roleMapper = mock(UserRoleMapper.class);
final var realmSettings = realmSettings().build();
final var config = new RealmConfig(realmId, realmSettings, env, threadContext);
final var client = mock(GraphServiceClient.class);
final var licenseState = mockLicense(true);
final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool);
assertThat(realm.token(threadContext), equalTo(null));
}
public void testAuthenticateAlwaysReturnsNotHandled() {
final var roleMapper = mock(UserRoleMapper.class);
final var realmSettings = realmSettings().build();
final var config = new RealmConfig(realmId, realmSettings, env, threadContext);
final var client = mock(GraphServiceClient.class);
final var licenseState = mockLicense(true);
final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool);
final var future = new PlainActionFuture<AuthenticationResult<User>>();
realm.authenticate(fakeToken, future);
final var result = future.actionGet();
assertThat(result, equalTo(AuthenticationResult.notHandled()));
}
private UserRoleMapper mockRoleMapper(Set<String> expectedGroups, Set<String> rolesToReturn) {
final var roleMapper = mock(UserRoleMapper.class);
doAnswer(invocation -> {
var userData = (UserRoleMapper.UserData) invocation.getArguments()[0];
assertEquals(userData.getGroups(), expectedGroups);
@SuppressWarnings("unchecked")
var listener = (ActionListener<Set<String>>) invocation.getArguments()[1];
listener.onResponse(rolesToReturn);
return null;
}).when(roleMapper).resolveRoles(any(), any());
return roleMapper;
}
private Settings.Builder realmSettings() {
final var secureSettings = new MockSecureSettings();
secureSettings.setString(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET), clientSecret);
return Settings.builder()
.put(globalSettings)
.put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0)
.put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_ID), clientId)
.put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.TENANT_ID), tenantId)
.setSecureSettings(secureSettings);
}
private XPackLicenseState mockLicense(boolean msGraphAllowed) {
final var licenseState = MockLicenseState.createMock();
when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(msGraphAllowed);
return licenseState;
}
private UserItemRequestBuilder mockGetUser(GraphServiceClient client) {
final var userRequestBuilder = mock(UsersRequestBuilder.class);
final var userItemRequestBuilder = mock(UserItemRequestBuilder.class);
when(client.users()).thenReturn(userRequestBuilder);
when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder);
return userItemRequestBuilder;
}
private GraphGroupRequestBuilder mockGetGroupMembership(UserItemRequestBuilder userItemRequestBuilder) {
final var memberOfRequestBuilder = mock(TransitiveMemberOfRequestBuilder.class);
final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class);
when(userItemRequestBuilder.transitiveMemberOf()).thenReturn(memberOfRequestBuilder);
when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder);
return graphGroupRequestBuilder;
}
private com.microsoft.graph.models.User user(String name, String email) {
final var msUser = new com.microsoft.graph.models.User();
msUser.setDisplayName(name);
msUser.setMail(email);
return msUser;
}
private GroupCollectionResponse groupMembership(String... groupIds) {
final var groupMembership = new GroupCollectionResponse();
groupMembership.setValue(Arrays.stream(groupIds).map(id -> {
var group = new Group();
group.setId(id);
return group;
}).toList());
return groupMembership;
}
private ODataError graphError(String message) {
final var error = new MainError();
error.setCode("badRequest");
error.setMessage(message);
final var graphError = new ODataError();
graphError.setError(error);
return graphError;
}
}

View file

@ -0,0 +1,76 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.core.security.support;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
/**
* A runnable that allows us to terminate and call the listener. We use this as a runnable can
* be queued and not executed for a long time or ever and this causes user requests to appear
* to hang. In these cases at least we can provide a response.
*/
public class CancellableRunnable<T> extends AbstractRunnable {
private final Runnable in;
private final ActionListener<T> listener;
private final Function<Exception, T> defaultValue;
private final Logger logger;
private final AtomicReference<RunnableState> state = new AtomicReference<>(RunnableState.AWAITING_EXECUTION);
public CancellableRunnable(ActionListener<T> listener, Function<Exception, T> defaultValue, Runnable in, Logger logger) {
this.listener = listener;
this.defaultValue = Objects.requireNonNull(defaultValue);
this.in = in;
this.logger = logger;
}
@Override
public void onFailure(Exception e) {
logger.error("execution of cancellable runnable failed", e);
final T result = defaultValue.apply(e);
listener.onResponse(result);
}
@Override
protected void doRun() throws Exception {
if (state.compareAndSet(RunnableState.AWAITING_EXECUTION, RunnableState.EXECUTING)) {
in.run();
} else {
logger.trace("skipping execution of cancellable runnable as the current state is [{}]", state.get());
}
}
@Override
public void onRejection(Exception e) {
listener.onFailure(e);
}
/**
* If the execution of this runnable has not already started, the runnable is cancelled and we pass an exception to the user
* listener
*/
public void maybeTimeout() {
if (state.compareAndSet(RunnableState.AWAITING_EXECUTION, RunnableState.TIMED_OUT)) {
logger.warn("skipping execution of cancellable runnable as it has been waiting for execution too long");
listener.onFailure(new ElasticsearchTimeoutException("timed out waiting for execution of cancellable runnable"));
}
}
private enum RunnableState {
AWAITING_EXECUTION,
EXECUTING,
TIMED_OUT
}
}

View file

@ -4,14 +4,13 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.security.authc.ldap;
package org.elasticsearch.xpack.core.security.support;
import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionTestUtils;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.ldap.LdapRealm.CancellableLdapRunnable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
@ -22,11 +21,11 @@ import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.sameInstance;
public class CancellableLdapRunnableTests extends ESTestCase {
public class CancellableRunnableTests extends ESTestCase {
public void testTimingOutARunnable() {
AtomicReference<Exception> exceptionAtomicReference = new AtomicReference<>();
final CancellableLdapRunnable<Object> runnable = new CancellableLdapRunnable<>(ActionListener.wrap(user -> {
final CancellableRunnable<Object> runnable = new CancellableRunnable<>(ActionListener.wrap(user -> {
throw new AssertionError("onResponse should not be called");
}, exceptionAtomicReference::set), e -> null, () -> { throw new AssertionError("runnable should not be executed"); }, logger);
@ -40,7 +39,7 @@ public class CancellableLdapRunnableTests extends ESTestCase {
public void testCallTimeOutAfterRunning() {
final AtomicBoolean ran = new AtomicBoolean(false);
final AtomicBoolean listenerCalled = new AtomicBoolean(false);
final CancellableLdapRunnable<Object> runnable = new CancellableLdapRunnable<>(ActionListener.wrap(user -> {
final CancellableRunnable<Object> runnable = new CancellableRunnable<>(ActionListener.wrap(user -> {
listenerCalled.set(true);
throw new AssertionError("onResponse should not be called");
}, e -> {
@ -59,7 +58,7 @@ public class CancellableLdapRunnableTests extends ESTestCase {
public void testRejectingExecution() {
AtomicReference<Exception> exceptionAtomicReference = new AtomicReference<>();
final CancellableLdapRunnable<Object> runnable = new CancellableLdapRunnable<>(ActionListener.wrap(user -> {
final CancellableRunnable<Object> runnable = new CancellableRunnable<>(ActionListener.wrap(user -> {
throw new AssertionError("onResponse should not be called");
}, exceptionAtomicReference::set), e -> null, () -> { throw new AssertionError("runnable should not be executed"); }, logger);
@ -75,7 +74,7 @@ public class CancellableLdapRunnableTests extends ESTestCase {
final CountDownLatch timeoutCalledLatch = new CountDownLatch(1);
final CountDownLatch runningLatch = new CountDownLatch(1);
final ActionListener<User> listener = ActionTestUtils.assertNoFailureListener(user -> listenerCalledLatch.countDown());
final CancellableLdapRunnable<User> runnable = new CancellableLdapRunnable<>(listener, e -> null, () -> {
final CancellableRunnable<User> runnable = new CancellableRunnable<>(listener, e -> null, () -> {
runningLatch.countDown();
try {
timeoutCalledLatch.await();
@ -98,7 +97,7 @@ public class CancellableLdapRunnableTests extends ESTestCase {
AtomicReference<String> resultRef = new AtomicReference<>();
final ActionListener<String> listener = ActionTestUtils.assertNoFailureListener(resultRef::set);
String defaultValue = randomAlphaOfLengthBetween(2, 10);
final CancellableLdapRunnable<String> runnable = new CancellableLdapRunnable<>(listener, e -> defaultValue, () -> {
final CancellableRunnable<String> runnable = new CancellableRunnable<>(listener, e -> defaultValue, () -> {
throw new RuntimeException("runnable intentionally failed");
}, logger);

View file

@ -66,7 +66,7 @@ dependencies {
}
api "org.slf4j:slf4j-api:${versions.slf4j}"
runtimeOnly "org.slf4j:slf4j-nop:${versions.slf4j}" // workaround for https://github.com/elastic/elasticsearch/issues/93714
// api "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}" see above
// api "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}" see above
api "org.apache.httpcomponents:httpclient:${versions.httpclient}"
api "org.apache.httpcomponents:httpcore:${versions.httpcore}"
@ -80,6 +80,7 @@ dependencies {
// Dependencies for oidc
api "com.nimbusds:oauth2-oidc-sdk:11.22.2"
runtimeOnly "com.nimbusds:content-type:2.3"
api project(path: xpackModule('security:lib:nimbus-jose-jwt-modified'), configuration: 'shadow')
if (isEclipse) {
/*
@ -183,6 +184,7 @@ tasks.named("dependencyLicenses").configure {
mapping from: /http.*/, to: 'httpclient'
mapping from: /bc.*/, to: 'bouncycastle'
mapping from: /failureaccess.*/, to: 'guava'
mapping from: 'content-type', to: 'nimbus'
}
tasks.named("forbiddenPatterns").configure {
@ -414,8 +416,6 @@ tasks.named("thirdPartyAudit").configure {
'javax.xml.bind.UnmarshallerHandler',
// Optional dependency of oauth2-oidc-sdk that we don't need since we do not support AES-SIV for JWE
'org.cryptomator.siv.SivMode',
'com.nimbusds.common.contenttype.ContentType',
'com.nimbusds.common.contenttype.ContentType$Parameter',
'javax.activation.ActivationDataFlavor',
'javax.activation.DataContentHandler',
'javax.activation.DataHandler',

View file

@ -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.

View file

@ -4,5 +4,11 @@ dependencies {
javaRestTestImplementation project(':x-pack:plugin:core')
javaRestTestImplementation project(':x-pack:plugin:security')
javaRestTestImplementation testArtifact(project(":x-pack:plugin:security:qa:saml-rest-tests"), "javaRestTest")
clusterPlugins project(':plugins:microsoft-graph-authz')
clusterPlugins project(':x-pack:extras:plugins:microsoft-graph-authz')
clusterModules project(":modules:analysis-common")
}
tasks.named("javaRestTest").configure {
// disable tests in FIPS mode as we need to use a custom truststore containing the certs used in MicrosoftGraphHttpFixture
buildParams.withFipsEnabledOnly(it)
}

View file

@ -7,21 +7,30 @@
package org.elasticsearch.xpack.security.authz.microsoft;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionTestUtils;
import org.elasticsearch.action.support.GroupedActionListener;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.core.PathUtils;
import org.elasticsearch.test.TestTrustStore;
import org.elasticsearch.test.XContentTestUtils;
import org.elasticsearch.test.cluster.ElasticsearchCluster;
import org.elasticsearch.test.cluster.local.model.User;
import org.elasticsearch.test.cluster.util.resource.Resource;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.test.rest.ObjectPath;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentType;
import org.elasticsearch.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.security.authc.saml.SamlIdpMetadataBuilder;
import org.elasticsearch.xpack.security.authc.saml.SamlResponseBuilder;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
@ -32,23 +41,62 @@ import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase {
private static final String TENANT_ID = "tenant-id";
private static final String CLIENT_ID = "client_id";
private static final String CLIENT_SECRET = "client_secret";
private static final String USERNAME = "Thor";
private static final String EXPECTED_GROUP = "test_group";
private static final List<MicrosoftGraphHttpFixture.TestUser> TEST_USERS = List.of(
new MicrosoftGraphHttpFixture.TestUser(
USERNAME,
"Thor Odinson",
"thor@oldap.test.elasticsearch.com",
List.of("unmapped-group-1", "unmapped-group-2", "unmapped-group-3", EXPECTED_GROUP),
List.of("microsoft_graph_user")
),
new MicrosoftGraphHttpFixture.TestUser(
"User2",
"User 2",
"user2@example.com",
List.of(EXPECTED_GROUP),
List.of("microsoft_graph_user")
),
new MicrosoftGraphHttpFixture.TestUser("User3", "User 3", "user3@example.com", List.of(), List.of())
);
private static final MicrosoftGraphHttpFixture graphFixture = new MicrosoftGraphHttpFixture(
TENANT_ID,
CLIENT_ID,
CLIENT_SECRET,
TEST_USERS,
3
);
public static ElasticsearchCluster cluster = initTestCluster();
private static final TestTrustStore trustStore = new TestTrustStore(
() -> MicrosoftGraphAuthzPluginIT.class.getClassLoader().getResourceAsStream("server/cert.pem")
);
@ClassRule
public static TestRule ruleChain = RuleChain.outerRule(cluster);
public static TestRule ruleChain = RuleChain.outerRule(graphFixture).around(trustStore).around(cluster);
private static final String IDP_ENTITY_ID = "http://idp.example.org/";
private static ElasticsearchCluster initTestCluster() {
return ElasticsearchCluster.local()
.module("analysis-common")
.setting("xpack.security.enabled", "true")
.setting("xpack.license.self_generated.type", "trial")
.setting("xpack.security.authc.token.enabled", "true")
@ -67,6 +115,17 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase {
.setting("xpack.security.authc.realms.saml.saml1.sp.logout", "http://logout/default")
.setting("xpack.security.authc.realms.saml.saml1.authorization_realms", "microsoft_graph1")
.setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.order", "2")
.setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.client_id", CLIENT_ID)
.keystore("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.client_secret", CLIENT_SECRET)
.setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.tenant_id", TENANT_ID)
.setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.graph_host", () -> graphFixture.getBaseUrl() + "/v1.0")
.setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.access_token_host", graphFixture::getBaseUrl)
.setting("logger.org.elasticsearch.xpack.security.authz.microsoft", "TRACE")
.setting("logger.com.microsoft", "TRACE")
.setting("logger.com.azure", "TRACE")
.systemProperty("javax.net.ssl.trustStore", () -> trustStore.getTrustStorePath().toString())
.systemProperty("javax.net.ssl.trustStoreType", "jks")
.systemProperty("tests.azure.credentials.disable_instance_discovery", "true")
.build();
}
@ -80,6 +139,35 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase {
return null;
}
@Before
public void setupRoleMapping() throws IOException {
Request request = new Request("PUT", "/_security/role_mapping/thor-kibana");
request.setJsonEntity(
Strings.toString(
XContentBuilder.builder(XContentType.JSON.xContent())
.startObject()
.array("roles", new String[] { "microsoft_graph_user" })
.field("enabled", true)
.startObject("rules")
.startArray("all")
.startObject()
.startObject("field")
.field("realm.name", "microsoft_graph1")
.endObject()
.endObject()
.startObject()
.startObject("field")
.field("groups", EXPECTED_GROUP)
.endObject()
.endObject()
.endArray() // "all"
.endObject() // "rules"
.endObject()
)
);
adminClient().performRequest(request);
}
@Override
protected String getTestRestCluster() {
return cluster.getHttpAddresses();
@ -96,14 +184,54 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase {
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
}
@Override
protected void configureClient(RestClientBuilder builder, Settings settings) throws IOException {
super.configureClient(builder, settings);
builder.setRequestConfigCallback(requestConfigBuilder -> {
requestConfigBuilder.setSocketTimeout(-1);
return requestConfigBuilder;
});
}
@Override
protected boolean shouldConfigureProjects() {
return false;
}
public void testAuthenticationSuccessful() throws Exception {
final String username = randomAlphaOfLengthBetween(4, 12);
samlAuthWithMicrosoftGraphAuthz(username, getSamlAssertionJsonBodyString(username));
final var listener = new PlainActionFuture<Map<String, Object>>();
samlAuthWithMicrosoftGraphAuthz(getSamlAssertionJsonBodyString(USERNAME), listener);
final var resp = listener.get();
List<String> roles = new XContentTestUtils.JsonMapView(resp).get("authentication.roles");
assertThat(resp.get("username"), equalTo(USERNAME));
assertThat(roles, contains("microsoft_graph_user"));
assertThat(ObjectPath.evaluate(resp, "authentication.authentication_realm.name"), equalTo("saml1"));
}
public void testConcurrentAuthentication() throws Exception {
final var concurrentLogins = 3;
final var resultsListener = new PlainActionFuture<Collection<Map<String, Object>>>();
final var groupedListener = new GroupedActionListener<>(TEST_USERS.size() * concurrentLogins, resultsListener);
for (int i = 0; i < concurrentLogins; i++) {
for (var user : TEST_USERS) {
samlAuthWithMicrosoftGraphAuthz(getSamlAssertionJsonBodyString(user.username()), groupedListener);
}
}
final var allResponses = resultsListener.get();
assertThat(allResponses.size(), equalTo(TEST_USERS.size() * concurrentLogins));
for (var user : TEST_USERS) {
var userResponses = allResponses.stream().filter(r -> r.get("username").equals(user.username())).toList();
assertThat(userResponses.size(), equalTo(concurrentLogins));
for (var response : userResponses) {
final List<String> roles = new XContentTestUtils.JsonMapView(response).get("authentication.roles");
assertThat(roles, equalTo(user.roles()));
assertThat(ObjectPath.evaluate(response, "authentication.authentication_realm.name"), equalTo("saml1"));
}
}
}
private String getSamlAssertionJsonBodyString(String username) throws Exception {
@ -120,15 +248,10 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase {
return Strings.toString(JsonXContent.contentBuilder().map(body));
}
private void samlAuthWithMicrosoftGraphAuthz(String username, String samlAssertion) throws Exception {
private void samlAuthWithMicrosoftGraphAuthz(String samlAssertion, ActionListener<Map<String, Object>> listener) {
var req = new Request("POST", "_security/saml/authenticate");
req.setJsonEntity(samlAssertion);
var resp = entityAsMap(client().performRequest(req));
List<String> roles = new XContentTestUtils.JsonMapView(entityAsMap(client().performRequest(req))).get("authentication.roles");
assertThat(resp.get("username"), equalTo(username));
// TODO add check for mapped groups and roles when available
assertThat(roles, empty());
assertThat(ObjectPath.evaluate(resp, "authentication.authentication_realm.name"), equalTo("saml1"));
client().performRequestAsync(req, ActionTestUtils.wrapAsRestResponseListener(listener.map(ESRestTestCase::entityAsMap)));
}
}

View file

@ -0,0 +1,296 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.security.authz.microsoft;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.ssl.KeyStoreUtil;
import org.elasticsearch.common.ssl.PemUtils;
import org.elasticsearch.core.Strings;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.RestUtils;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentType;
import org.junit.rules.ExternalResource;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
public class MicrosoftGraphHttpFixture extends ExternalResource {
private static final Logger logger = LogManager.getLogger(MicrosoftGraphHttpFixture.class);
private final String tenantId;
private final String clientId;
private final String clientSecret;
private final List<TestUser> users;
private final int groupsPageSize;
private final String jwt = "test jwt";
private final AtomicInteger loginCount = new AtomicInteger(0);
private final AtomicInteger getUserPropertiesCount = new AtomicInteger(0);
private final AtomicInteger getGroupMembershipCount = new AtomicInteger(0);
private HttpsServer server;
public MicrosoftGraphHttpFixture(String tenantId, String clientId, String clientSecret, List<TestUser> users, int groupsPageSize) {
this.tenantId = tenantId;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.users = users;
this.groupsPageSize = groupsPageSize;
}
@Override
protected void before() throws Throwable {
final var certificate = PemUtils.readCertificates(
List.of(Path.of(getClass().getClassLoader().getResource("server/cert.pem").toURI()))
).getFirst();
final var key = PemUtils.readPrivateKey(Path.of(getClass().getClassLoader().getResource("server/cert.key").toURI()), () -> null);
final var sslContext = SSLContext.getInstance("TLS");
sslContext.init(
new KeyManager[] { KeyStoreUtil.createKeyManager(new Certificate[] { certificate }, key, null) },
null,
new SecureRandom()
);
server = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
server.setHttpsConfigurator(new HttpsConfigurator(sslContext));
registerGetAccessTokenHandler();
for (TestUser user : users) {
registerGetUserHandler(user);
registerGetUserMembershipHandler(user);
}
server.createContext("/", exchange -> {
logger.warn("Unhandled request for [{}]", exchange.getRequestURI());
exchange.sendResponseHeaders(RestStatus.NOT_IMPLEMENTED.getStatus(), 0);
exchange.close();
});
server.start();
logger.info("Started server on port [{}]", server.getAddress().getPort());
}
@Override
protected void after() {
server.stop(0);
}
public String getBaseUrl() {
return "https://" + server.getAddress().getHostString() + ":" + server.getAddress().getPort();
}
private void registerGetAccessTokenHandler() {
server.createContext("/" + tenantId + "/oauth2/v2.0/token", exchange -> {
logger.info("Received access token request");
final var callCount = loginCount.incrementAndGet();
if (callCount == 1) {
graphError(exchange, RestStatus.GATEWAY_TIMEOUT, "Gateway timed out");
return;
}
if (exchange.getRequestMethod().equals("POST") == false) {
graphError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected POST request");
return;
}
final var requestBody = Streams.copyToString(new InputStreamReader(exchange.getRequestBody(), Charset.defaultCharset()));
final var formFields = new HashMap<String, String>();
RestUtils.decodeQueryString(requestBody, 0, formFields);
if (formFields.get("grant_type").equals("client_credentials") == false) {
graphError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Grant Type: %s", formFields.get("grant_type")));
return;
}
if (formFields.get("client_id").equals(clientId) == false) {
graphError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Client ID: %s", formFields.get("client_id")));
return;
}
if (formFields.get("client_secret").equals(clientSecret) == false) {
graphError(
exchange,
RestStatus.BAD_REQUEST,
Strings.format("Unexpected Client Secret: %s", formFields.get("client_secret"))
);
return;
}
if (formFields.get("scope").contains("https://graph.microsoft.com/.default") == false) {
graphError(
exchange,
RestStatus.BAD_REQUEST,
Strings.format("Missing required https://graph.microsoft.com/.default scope: [%s]", formFields.get("scope"))
);
return;
}
final var token = XContentBuilder.builder(XContentType.JSON.xContent());
token.startObject();
token.field("access_token", jwt);
token.field("expires_in", 86400L);
token.field("ext_expires_in", 86400L);
token.field("token_type", "Bearer");
token.endObject();
var responseBytes = BytesReference.bytes(token);
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(RestStatus.OK.getStatus(), responseBytes.length());
responseBytes.writeTo(exchange.getResponseBody());
exchange.close();
});
}
private void registerGetUserHandler(TestUser user) {
server.createContext("/v1.0/users/" + user.username(), exchange -> {
logger.info("Received get user properties request [{}]", exchange.getRequestURI());
final var callCount = getUserPropertiesCount.incrementAndGet();
if (exchange.getRequestMethod().equals("GET") == false) {
graphError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected GET request");
return;
}
final var authorization = exchange.getRequestHeaders().getFirst("Authorization");
if (authorization.equals("Bearer " + jwt) == false) {
graphError(exchange, RestStatus.UNAUTHORIZED, Strings.format("Wrong Authorization header: %s", authorization));
return;
}
if (exchange.getRequestURI().getQuery().contains("$select=displayName,mail") == false) {
graphError(exchange, RestStatus.BAD_REQUEST, "Must filter fields using $select");
return;
}
// ensure the client retries temporary errors
if (callCount == 1) {
graphError(exchange, RestStatus.GATEWAY_TIMEOUT, "Gateway timed out");
return;
} else if (callCount == 2) {
graphError(exchange, RestStatus.TOO_MANY_REQUESTS, "Too many requests");
return;
}
var userProperties = XContentBuilder.builder(XContentType.JSON.xContent());
userProperties.startObject();
userProperties.field("displayName", user.displayName());
userProperties.field("mail", user.email());
userProperties.endObject();
var responseBytes = BytesReference.bytes(userProperties);
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(RestStatus.OK.getStatus(), responseBytes.length());
responseBytes.writeTo(exchange.getResponseBody());
exchange.close();
});
}
private void registerGetUserMembershipHandler(TestUser user) {
final var skipToken = UUID.randomUUID().toString();
server.createContext("/v1.0/users/" + user.username() + "/transitiveMemberOf", exchange -> {
logger.info("Received get user membership request [{}]", exchange.getRequestURI());
final var callCount = getGroupMembershipCount.incrementAndGet();
if (callCount == 1) {
graphError(exchange, RestStatus.GATEWAY_TIMEOUT, "Gateway timed out");
return;
}
if (exchange.getRequestMethod().equals("GET") == false) {
graphError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected GET request");
return;
}
final var authorization = exchange.getRequestHeaders().getFirst("Authorization");
if (authorization.equals("Bearer " + jwt) == false) {
graphError(exchange, RestStatus.UNAUTHORIZED, Strings.format("Wrong Authorization header: %s", authorization));
return;
}
if (exchange.getRequestURI().getQuery().contains("$select=id") == false) {
// this test server only returns `id`s, so if the client is expecting other fields, it won't work anyway
graphError(exchange, RestStatus.BAD_REQUEST, "Must filter fields using $select");
return;
}
String nextLink = null;
Object[] groups;
// return multiple pages of results, to ensure client correctly supports paging
if (exchange.getRequestURI().getQuery().contains("$skiptoken")) {
groups = user.groups().stream().skip(groupsPageSize).map(id -> Map.of("id", id)).toArray();
} else {
groups = user.groups().stream().limit(groupsPageSize).map(id -> Map.of("id", id)).toArray();
if (user.groups().size() > groupsPageSize) {
nextLink = getBaseUrl() + exchange.getRequestURI().toString() + "&$skiptoken=" + skipToken;
}
}
final var groupMembership = XContentBuilder.builder(XContentType.JSON.xContent());
groupMembership.startObject();
groupMembership.field("@odata.nextLink", nextLink);
groupMembership.array("value", groups);
groupMembership.endObject();
var responseBytes = BytesReference.bytes(groupMembership);
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(RestStatus.OK.getStatus(), responseBytes.length());
responseBytes.writeTo(exchange.getResponseBody());
exchange.close();
});
}
// attempt to comply with https://learn.microsoft.com/en-us/graph/errors
private void graphError(HttpExchange exchange, RestStatus statusCode, String message) throws IOException {
logger.warn(message);
final var errorResponse = XContentBuilder.builder(XContentType.JSON.xContent());
errorResponse.startObject();
errorResponse.startObject("error");
errorResponse.field("code", statusCode.toString());
errorResponse.field("message", message);
errorResponse.endObject();
errorResponse.endObject();
final var responseBytes = BytesReference.bytes(errorResponse);
exchange.getResponseHeaders().add("Content-Type", "application/json");
exchange.sendResponseHeaders(statusCode.getStatus(), responseBytes.length());
responseBytes.writeTo(exchange.getResponseBody());
exchange.close();
}
public record TestUser(String username, String displayName, String email, List<String> groups, List<String> roles) {}
}

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCswbyZDtaghZXs
Phs1+lqCnq5HmRT2P6Drrs9bJlABeql29IhzdHOGLr+lTMhKOUpHuphgC31qbf/G
vQLS65qdOzTjNfLv93+Jj0gp4S7Q6eRZvn1ihUgzECHazTYwIlzVs4sFPm5i2fQb
DK7W6zQm+h9r6GjCYj01OeIAe7rbRI9Ar+svuHGfZnaQHzLZlfYkkM2bCaXBgKWV
wmEUmwMW+IMOPCrVm+gk1MDbGnu9KtY/LqrJcddsqOdkK8qJ0Lpchg3zlP4qIzbm
WRyTUIy1USbcazjuC/vMmN4fr/Xr0Jrhi4Rw8l2LGdyA8qnqtKYTqMzo3uv1ESlE
R8EAZDUbAgMBAAECggEAY8lYRdSTVo8y5Q2OrCQa6b38jvC2cfKY4enMbir4JZKT
lllzA7VtEUGpgzKRsoXbCQmYAEpCvBojlskQe4KJgW50gxVjaQa9zVhM55vhbdzc
AJaOWD0CUjRsSbUlKrJ+ixW1JGdGXaTlYkZ2K0AalLT/N1Y8RKN4FWmEyKCvcvz4
0XzOIVmG+HqcNURamXTxMKbj1yzi5goue2/iP2kMFo8sHxRsGvvV4PWo6JrktE2y
47oiH42lpSIcpLSE8z/csLbMTw/Q/pPQAYqyvEJHU22uhac1XmMqFHWNSpQZq6gr
46t8YQ3FJSN8UrZf1h1mdvLlK/sgPEvCQa6TrCq4GQKBgQDbl0M/4gJZhgpvBuCC
aratamWcFoa/pu1/JoQhPXLv6uGwB/cFhJUVr0ZoI5KPFJr9SG4kQ/eEPywkY2qT
mGPVlVmGOeJa1VK8TRUTzkEFTYWytUepACM2//LiWvzABciO8SxWgNZrmUKghQTN
d989b8edy0ti6y7lHpkTyawVXQKBgQDJZo7X6K+6cyuXORApv98cU5C3vEVtBp/c
QfU/rRj/YXAbYFhKIS5rF/Gfg2YoDa5YguMxGsIPzYwdTI5gGGgSgolapz3fr22q
edCPaFg8qO64pIii+Ar4lx4k1IyNtpJ+nvlam7sI9yGzksrVazsWhpaSKX8xGd7r
9ZSr/c8U1wKBgGWl+pJay52nR7MnasvUHCXgR5LedpfG7M9cA/PjHw5iGwDCXx2l
xuFX1m6kcNZcwnYWji2pbK1CFOvvPUl/VE9tKBjTOK21a+wQfn5BjqWmwgn8kmRv
1N1D06nmVnOI+dL5Xv3X++mo80ec66E1KRimYq/viEEM/xM+e7vGMitdAoGAUAUe
pix+fa8615fFk0D37bJKIqZ8Uyg5pfLS9ZzZ/MYDG+14xuNOJSDbUMyNb0aYSfSf
PihqiIrbq9x6CTZJS2lwF4Oxcsmp4f0KX6BOxrM8PkKpQ08YVNL+GBYXTksHA6Y4
XsbXVmWSj124l3lGfdm1w5cXQTQNPWVSz89FUvsCgYEArl27gbXndTLpZ4dNkeBS
0JMe84nPPrrwHbNwcqkoDiux5Ln+AZE8jYSlp4kUfd1A7XDTxXWXIxeD0YLiabf3
5+/QzJ6j1qi77JoyadomnL7CFI5f2FotKt029PeVxAUOohao94p8J5OuaRMPvkGC
CNhjfRAIBhfm9kdyjPmwVLU=
-----END PRIVATE KEY-----

View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDRTCCAi2gAwIBAgIVAJpxxIbXWyvdd6/rIFXPgWe6fyvTMA0GCSqGSIb3DQEB
CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu
ZXJhdGVkIENBMB4XDTE4MTIyMTA3NDY1NVoXDTQ2MDUwNzA3NDY1NVowGTEXMBUG
A1UEAxMObGRhcC10ZXN0LWNhc2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCswbyZDtaghZXsPhs1+lqCnq5HmRT2P6Drrs9bJlABeql29IhzdHOGLr+l
TMhKOUpHuphgC31qbf/GvQLS65qdOzTjNfLv93+Jj0gp4S7Q6eRZvn1ihUgzECHa
zTYwIlzVs4sFPm5i2fQbDK7W6zQm+h9r6GjCYj01OeIAe7rbRI9Ar+svuHGfZnaQ
HzLZlfYkkM2bCaXBgKWVwmEUmwMW+IMOPCrVm+gk1MDbGnu9KtY/LqrJcddsqOdk
K8qJ0Lpchg3zlP4qIzbmWRyTUIy1USbcazjuC/vMmN4fr/Xr0Jrhi4Rw8l2LGdyA
8qnqtKYTqMzo3uv1ESlER8EAZDUbAgMBAAGjaTBnMB0GA1UdDgQWBBQaiCDScfBa
jHOSk04XOymffbLBxTAfBgNVHSMEGDAWgBROJaHRWe17um5rqqYn10aqedr55DAa
BgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwCQYDVR0TBAIwADANBgkqhkiG9w0B
AQsFAAOCAQEAXBovNqVg+VQ1LR0PfEMpbgbQlekky8qY2y1tz7J0ntGepAq+Np6n
7J9En6ty1ELZUvgPUCF2btQqZbv8uyHz/C+rojKC5xzHN5qbZ31o5/0I/kNase1Z
NbXuNJe3wAXuz+Mj5rtuOGZvlFsbtocuoydVYOclfqjUXcoZtqCcRamSvye7vGl2
CHPqDi0uK8d75nE9Jrnmz/BNNV7CjPg636PJmCUrLL21+t69ZFL1eGAFtLBmmjcw
cMkyv9bJirjZbjt/9UB+fW9XzV3RVLAzfrIHtToupXmWc4+hTOnlbKfFwqB9fa7Y
XcCfGrZoJg9di1HbJrSJmv5QgRTM+/zkrA==
-----END CERTIFICATE-----

View file

@ -8,13 +8,10 @@ package org.elasticsearch.xpack.security.authc.ldap;
import com.unboundid.ldap.sdk.LDAPException;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ContextPreservingActionListener;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.core.IOUtils;
@ -32,6 +29,7 @@ import org.elasticsearch.xpack.core.security.authc.ldap.LdapUserSearchSessionFac
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper;
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper.UserData;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.core.security.support.CancellableRunnable;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.security.authc.ldap.support.LdapLoadBalancing;
@ -46,10 +44,8 @@ import org.elasticsearch.xpack.security.support.ReloadableSecurityComponent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -150,7 +146,7 @@ public final class LdapRealm extends CachingUsernamePasswordRealm implements Rel
assert delegatedRealms != null : "Realm has not been initialized correctly";
// we submit to the threadpool because authentication using LDAP will execute blocking I/O for a bind request and we don't want
// network threads stuck waiting for a socket to connect. After the bind, then all interaction with LDAP should be async
final CancellableLdapRunnable<AuthenticationResult<User>> cancellableLdapRunnable = new CancellableLdapRunnable<>(
final CancellableRunnable<AuthenticationResult<User>> cancellableLdapRunnable = new CancellableRunnable<>(
listener,
ex -> AuthenticationResult.unsuccessful("Authentication against realm [" + this.toString() + "] failed", ex),
() -> sessionFactory.session(
@ -173,7 +169,7 @@ public final class LdapRealm extends CachingUsernamePasswordRealm implements Rel
result -> userActionListener.onResponse(result.getValue()),
userActionListener::onFailure
);
final CancellableLdapRunnable<User> cancellableLdapRunnable = new CancellableLdapRunnable<>(
final CancellableRunnable<User> cancellableLdapRunnable = new CancellableRunnable<>(
userActionListener,
e -> null,
() -> sessionFactory.unauthenticatedSession(
@ -323,65 +319,5 @@ public final class LdapRealm extends CachingUsernamePasswordRealm implements Rel
}
resultListener.onResponse(AuthenticationResult.unsuccessful(action + " failed", e));
}
}
/**
* A runnable that allows us to terminate and call the listener. We use this as a runnable can
* be queued and not executed for a long time or ever and this causes user requests to appear
* to hang. In these cases at least we can provide a response.
*/
static class CancellableLdapRunnable<T> extends AbstractRunnable {
private final Runnable in;
private final ActionListener<T> listener;
private final Function<Exception, T> defaultValue;
private final Logger logger;
private final AtomicReference<LdapRunnableState> state = new AtomicReference<>(LdapRunnableState.AWAITING_EXECUTION);
CancellableLdapRunnable(ActionListener<T> listener, Function<Exception, T> defaultValue, Runnable in, Logger logger) {
this.listener = listener;
this.defaultValue = Objects.requireNonNull(defaultValue);
this.in = in;
this.logger = logger;
}
@Override
public void onFailure(Exception e) {
logger.error("execution of ldap runnable failed", e);
final T result = defaultValue.apply(e);
listener.onResponse(result);
}
@Override
protected void doRun() throws Exception {
if (state.compareAndSet(LdapRunnableState.AWAITING_EXECUTION, LdapRunnableState.EXECUTING)) {
in.run();
} else {
logger.trace("skipping execution of ldap runnable as the current state is [{}]", state.get());
}
}
@Override
public void onRejection(Exception e) {
listener.onFailure(e);
}
/**
* If the execution of this runnable has not already started, the runnable is cancelled and we pass an exception to the user
* listener
*/
void maybeTimeout() {
if (state.compareAndSet(LdapRunnableState.AWAITING_EXECUTION, LdapRunnableState.TIMED_OUT)) {
logger.warn("skipping execution of ldap runnable as it has been waiting for " + "execution too long");
listener.onFailure(new ElasticsearchTimeoutException("timed out waiting for " + "execution of ldap runnable"));
}
}
enum LdapRunnableState {
AWAITING_EXECUTION,
EXECUTING,
TIMED_OUT
}
}
}