From a4d85f6487f51077673aaefd31288e5291c5b5c2 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Tue, 6 May 2025 16:30:01 +0100 Subject: [PATCH 01/66] basic MS graph authz implementation --- plugins/microsoft-graph-authz/build.gradle | 11 +- .../licenses/nimbus-jose-jwt-LICENSE.txt | 202 +++++++++++++++++ .../licenses/nimbus-jose-jwt-NOTICE.txt | 14 ++ .../src/main/java/module-info.java | 3 + .../microsoft/MicrosoftGraphAuthzPlugin.java | 2 +- .../microsoft/MicrosoftGraphAuthzRealm.java | 135 +++++++++++- .../MicrosoftGraphAuthzRealmSettings.java | 39 +++- .../microsoft-graph-authz-tests/build.gradle | 4 + .../microsoft/AzureGraphHttpFixture.java | 207 ++++++++++++++++++ .../MicrosoftGraphAuthzPluginIT.java | 72 +++++- 10 files changed, 671 insertions(+), 18 deletions(-) create mode 100644 plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-NOTICE.txt create mode 100644 x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index f2b30c57c3755..260b99e019f96 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -10,12 +10,13 @@ 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"] + 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") + compileOnly project(":x-pack:plugin:core") + compileOnly "com.nimbusds:nimbus-jose-jwt:10.0.2" } diff --git a/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-LICENSE.txt @@ -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. diff --git a/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-NOTICE.txt new file mode 100644 index 0000000000000..cb9ad94f662a6 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-NOTICE.txt @@ -0,0 +1,14 @@ +Nimbus JOSE + JWT + +Copyright 2012 - 2018, Connect2id Ltd. + +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. diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/plugins/microsoft-graph-authz/src/main/java/module-info.java index 9a149da12f6e9..a59188bbe1341 100644 --- a/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ b/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -14,6 +14,9 @@ requires org.elasticsearch.server; requires org.elasticsearch.xcore; requires org.elasticsearch.logging; + requires org.apache.httpcomponents.httpclient; + requires org.apache.httpcomponents.httpcore; + requires com.nimbusds.jose.jwt; provides org.elasticsearch.xpack.core.security.SecurityExtension with MicrosoftGraphAuthzPlugin; } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java index 71d06d403356e..6986090a83929 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java @@ -20,7 +20,7 @@ public class MicrosoftGraphAuthzPlugin extends Plugin implements SecurityExtension { @Override public Map getRealms(SecurityComponents components) { - return Map.of(MicrosoftGraphAuthzRealmSettings.REALM_TYPE, MicrosoftGraphAuthzRealm::new); + return Map.of(MicrosoftGraphAuthzRealmSettings.REALM_TYPE, config -> new MicrosoftGraphAuthzRealm(components.roleMapper(), config)); } @Override diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 4408216545fb6..155ec86e293cc 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -9,22 +9,50 @@ package org.elasticsearch.xpack.security.authz.microsoft; +import com.nimbusds.jose.util.JSONObjectUtils; + +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.core.Tuple; 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.authc.support.UserRoleMapper; import org.elasticsearch.xpack.core.security.user.User; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.text.ParseException; +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); - public MicrosoftGraphAuthzRealm(RealmConfig config) { + private final HttpClient httpClient; + private final RealmConfig config; + private final UserRoleMapper roleMapper; + + public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { super(config); + + this.roleMapper = roleMapper; + this.config = config; + this.httpClient = HttpClients.createDefault(); } @Override @@ -43,8 +71,107 @@ public void authenticate(AuthenticationToken token, ActionListener listener) { - logger.info("Microsoft Graph Authz not yet implemented, returning empty roles for [{}]", username); - listener.onResponse(new User(username)); + public void lookupUser(String principal, ActionListener listener) { + try { + final var token = fetchAccessToken(); + final var userProperties = fetchUserProperties(principal, token); + final var groups = fetchGroupMembership(principal, token); + + 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.debug("Entra ID user {}", user); + l.onResponse(user); + })); + } catch (Exception e) { + listener.onFailure(e); + } } + + private String fetchAccessToken() throws IOException, ParseException { + var request = new HttpPost( + Strings.format( + "%s/%s/oauth2/v2.0/token", + config.getSetting(MicrosoftGraphAuthzRealmSettings.ACCESS_TOKEN_HOST), + config.getSetting(MicrosoftGraphAuthzRealmSettings.TENANT_ID) + ) + ); + request.setEntity( + new UrlEncodedFormEntity( + List.of( + new BasicNameValuePair("grant_type", "client_credentials"), + new BasicNameValuePair("scope", "https://graph.microsoft.com/.default"), + new BasicNameValuePair("client_id", config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_ID)), + new BasicNameValuePair("client_secret", config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET)) + ) + ) + ); + logger.trace("getting bearer token from {}", request.getURI()); + final var response = httpClient.execute(request, new BasicResponseHandler()); + + final var json = JSONObjectUtils.parse(response); + final var token = (String) json.get("access_token"); + logger.trace("Azure access token [{}]", token); + + return token; + } + + private Tuple fetchUserProperties(String userId, String token) throws IOException, ParseException { + var request = new HttpGet( + Strings.format( + "%s/v1.0/users/%s?$select=displayName,mail", + config.getSetting(MicrosoftGraphAuthzRealmSettings.API_HOST), + userId + ) + ); + request.addHeader("Authorization", "Bearer " + token); + logger.trace("getting user info from {}", request.getURI()); + final var response = httpClient.execute(request, new BasicResponseHandler()); + + final var json = JSONObjectUtils.parse(response); + final var email = (String) json.get("mail"); + final var name = (String) json.get("displayName"); + + logger.trace("User [{}] has email [{}]", name, email); + + return Tuple.tuple(name, email); + } + + private List fetchGroupMembership(String userId, String token) throws IOException, ParseException, URISyntaxException { + var request = new HttpGet(); + request.addHeader("Authorization", "Bearer " + token); + + var nextPage = Strings.format( + "%s/v1.0/users/%s/memberOf/microsoft.graph.group?$select=id&$top=999", + config.getSetting(MicrosoftGraphAuthzRealmSettings.API_HOST), + userId + ); + var groups = new ArrayList(); + + while (nextPage != null) { + request.setURI(new URI(nextPage)); + logger.trace("getting group membership from {}", request.getURI()); + final var response = httpClient.execute(request, new BasicResponseHandler()); + + var json = JSONObjectUtils.parse(response); + nextPage = (String) json.get("@odata.nextLink"); + for (var groupData: (List) json.get("value")) { + final var properties = (Map) groupData; + groups.add((String) properties.get("id")); + } + } + + logger.trace("Got {} groups from Graph {}", groups.size(), String.join(", ", groups)); + + return groups; + } + } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java index 46965968b79a6..e2948a757eeb5 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java @@ -18,7 +18,44 @@ public class MicrosoftGraphAuthzRealmSettings { public static final String REALM_TYPE = "microsoft_graph"; + public static final Setting.AffixSetting CLIENT_ID = RealmSettings.simpleString( + REALM_TYPE, + "client_id", + Setting.Property.NodeScope + ); + + public static final Setting.AffixSetting CLIENT_SECRET = RealmSettings.simpleString( + REALM_TYPE, + "client_secret", + Setting.Property.NodeScope + ); + + public static final Setting.AffixSetting TENANT_ID = RealmSettings.simpleString( + REALM_TYPE, + "tenant_id", + Setting.Property.NodeScope + ); + + public static final Setting.AffixSetting 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 API_HOST = Setting.affixKeySetting( + RealmSettings.realmSettingPrefix(REALM_TYPE), + "graph_host", + key -> Setting.simpleString(key, "https://graph.microsoft.com", Setting.Property.NodeScope) + ); + public static List> getSettings() { - return new ArrayList<>(RealmSettings.getStandardSettings(REALM_TYPE)); + var settings = new ArrayList>(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); + + return settings; } } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle index 847179ff60e6a..bd8f02069f184 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle @@ -6,3 +6,7 @@ dependencies { javaRestTestImplementation testArtifact(project(":x-pack:plugin:security:qa:saml-rest-tests"), "javaRestTest") clusterPlugins project(':plugins:microsoft-graph-authz') } + +tasks.named('javaRestTest') { + usesDefaultDistribution("Reason why default distribution is required") +} diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java new file mode 100644 index 0000000000000..c591472ec2a68 --- /dev/null +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java @@ -0,0 +1,207 @@ +/* + * 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.HttpServer; + +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.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.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class AzureGraphHttpFixture extends ExternalResource { + + private static final Logger logger = LogManager.getLogger(AzureGraphHttpFixture.class); + + private final String tenantId; + private final String clientId; + private final String clientSecret; + private final String principal; + private final String displayName; + private final String email; + + private HttpServer server; + + public AzureGraphHttpFixture( + String tenantId, + String clientId, + String clientSecret, + String principal, + String displayName, + String email + ) { + this.tenantId = tenantId; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.principal = principal; + this.displayName = displayName; + this.email = email; + } + + @Override + protected void before() throws Throwable { + final var jwt = "test jwt"; + final var skipToken = UUID.randomUUID().toString(); + + server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); + server.createContext("/" + tenantId + "/oauth2/v2.0/token", exchange -> { + if (exchange.getRequestMethod().equals("POST") == false) { + httpError(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(); + RestUtils.decodeQueryString(requestBody, 0, formFields); + + if (formFields.get("grant_type").equals("client_credentials") == false) { + httpError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Grant Type: %s", formFields.get("grant_type"))); + return; + } + if (formFields.get("client_id").equals(clientId) == false) { + httpError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Client ID: %s", formFields.get("client_id"))); + return; + } + if (formFields.get("client_secret").equals(clientSecret) == false) { + httpError( + exchange, + RestStatus.BAD_REQUEST, + Strings.format("Unexpected Client Secret: %s", formFields.get("client_secret")) + ); + return; + } + if (formFields.get("scope").equals("https://graph.microsoft.com/.default") == false) { + httpError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected 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(); + }); + server.createContext("/v1.0/users/" + principal, exchange -> { + if (exchange.getRequestMethod().equals("GET") == false) { + httpError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected GET request"); + return; + } + + final var authorization = exchange.getRequestHeaders().getFirst("Authorization"); + if (authorization.equals("Bearer " + jwt) == false) { + httpError(exchange, RestStatus.UNAUTHORIZED, Strings.format("Wrong Authorization header: %s", authorization)); + return; + } + + if (exchange.getRequestURI().getQuery().contains("$select=displayName,mail") == false) { + httpError(exchange, RestStatus.BAD_REQUEST, "Must filter fields using $select"); + return; + } + + var userProperties = XContentBuilder.builder(XContentType.JSON.xContent()); + userProperties.startObject(); + userProperties.field("displayName", displayName); + userProperties.field("mail", 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(); + }); + server.createContext("/v1.0/users/" + principal + "/memberOf/microsoft.graph.group", exchange -> { + if (exchange.getRequestMethod().equals("GET") == false) { + httpError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected GET request"); + return; + } + + final var authorization = exchange.getRequestHeaders().getFirst("Authorization"); + if (authorization.equals("Bearer " + jwt) == false) { + httpError(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 + httpError(exchange, RestStatus.BAD_REQUEST, "Must filter fields using $select"); + return; + } + + var nextLink = getBaseUrl() + exchange.getRequestURI().toString() + "&$skiptoken=" + skipToken; + var groups = new Object[] { Map.of("id", "group-id-1"), Map.of("id", "group-id-2") }; + + // return multiple pages of results, to ensure client correctly supports paging + if (exchange.getRequestURI().getQuery().contains("$skiptoken")) { + groups = new Object[] { Map.of("id", "group-id-3") }; + nextLink = null; + } + + 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(); + }); + server.start(); + } + + @Override + protected void after() { + server.stop(0); + } + + public String getBaseUrl() { + return "http://" + server.getAddress().getHostString() + ":" + server.getAddress().getPort(); + } + + private void httpError(HttpExchange exchange, RestStatus statusCode, String message) throws IOException { + logger.warn(message); + + final var responseBytes = message.getBytes(); + exchange.sendResponseHeaders(statusCode.getStatus(), responseBytes.length); + exchange.getResponseBody().write(responseBytes); + + exchange.close(); + } +} diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index da8b4e198f61d..6abc2f585012b 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -15,13 +15,17 @@ import org.elasticsearch.core.PathUtils; import org.elasticsearch.test.XContentTestUtils; import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.test.cluster.local.distribution.DistributionType; 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; @@ -36,19 +40,35 @@ 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 AzureGraphHttpFixture graphFixture = new AzureGraphHttpFixture( + TENANT_ID, + CLIENT_ID, + CLIENT_SECRET, + USERNAME, + "Thor Odinson", + "thor@oldap.test.elasticsearch.com" + ); + public static ElasticsearchCluster cluster = initTestCluster(); @ClassRule - public static TestRule ruleChain = RuleChain.outerRule(cluster); + public static TestRule ruleChain = RuleChain.outerRule(graphFixture).around(cluster); private static final String IDP_ENTITY_ID = "http://idp.example.org/"; private static ElasticsearchCluster initTestCluster() { return ElasticsearchCluster.local() + .distribution(DistributionType.DEFAULT) .setting("xpack.security.enabled", "true") .setting("xpack.license.self_generated.type", "trial") .setting("xpack.security.authc.token.enabled", "true") @@ -67,6 +87,12 @@ private static ElasticsearchCluster initTestCluster() { .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) + .setting("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) + .setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.access_token_host", graphFixture::getBaseUrl) + .setting("logger.org.elasticsearch.xpack.security.authz.microsoft", "TRACE") .build(); } @@ -80,6 +106,40 @@ private static String getIDPMetadata() { 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("username", USERNAME) + .endObject() + .endObject() + .startObject() + .startObject("field") + .field("realm.name", "microsoft_graph1") + .endObject() + .endObject() + .startObject() + .startObject("field") + .field("groups", "group-id-3") + .endObject() + .endObject() + .endArray() // "all" + .endObject() // "rules" + .endObject() + ) + ); + adminClient().performRequest(request); + } + @Override protected String getTestRestCluster() { return cluster.getHttpAddresses(); @@ -102,8 +162,7 @@ protected boolean shouldConfigureProjects() { } public void testAuthenticationSuccessful() throws Exception { - final String username = randomAlphaOfLengthBetween(4, 12); - samlAuthWithMicrosoftGraphAuthz(username, getSamlAssertionJsonBodyString(username)); + samlAuthWithMicrosoftGraphAuthz(USERNAME, getSamlAssertionJsonBodyString(USERNAME)); } private String getSamlAssertionJsonBodyString(String username) throws Exception { @@ -124,10 +183,9 @@ private void samlAuthWithMicrosoftGraphAuthz(String username, String samlAsserti var req = new Request("POST", "_security/saml/authenticate"); req.setJsonEntity(samlAssertion); var resp = entityAsMap(client().performRequest(req)); - List roles = new XContentTestUtils.JsonMapView(entityAsMap(client().performRequest(req))).get("authentication.roles"); + List roles = new XContentTestUtils.JsonMapView(resp).get("authentication.roles"); assertThat(resp.get("username"), equalTo(username)); - // TODO add check for mapped groups and roles when available - assertThat(roles, empty()); + assertThat(roles, contains("microsoft_graph_user")); assertThat(ObjectPath.evaluate(resp, "authentication.authentication_realm.name"), equalTo("saml1")); } From cfd290693b062e4cfd3853fe1e219c401fb66b6d Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Mon, 19 May 2025 16:27:03 +0000 Subject: [PATCH 02/66] [CI] Auto commit changes from spotless --- .../security/authz/microsoft/MicrosoftGraphAuthzRealm.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 155ec86e293cc..4e9fe68b6c008 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -163,7 +163,7 @@ private List fetchGroupMembership(String userId, String token) throws IO var json = JSONObjectUtils.parse(response); nextPage = (String) json.get("@odata.nextLink"); - for (var groupData: (List) json.get("value")) { + for (var groupData : (List) json.get("value")) { final var properties = (Map) groupData; groups.add((String) properties.get("id")); } From 9182858083153a61bdf73c665bb2ff77eb107541 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Tue, 20 May 2025 10:41:44 +0100 Subject: [PATCH 03/66] move client_secret to keystore --- .../security/authz/microsoft/MicrosoftGraphAuthzRealm.java | 6 +++++- .../authz/microsoft/MicrosoftGraphAuthzRealmSettings.java | 7 ++++--- .../security/qa/microsoft-graph-authz-tests/build.gradle | 5 +---- .../authz/microsoft/MicrosoftGraphAuthzPluginIT.java | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 4e9fe68b6c008..6379073a5dfec 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -20,6 +20,8 @@ import org.apache.http.message.BasicNameValuePair; import org.elasticsearch.action.ActionListener; 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.Tuple; import org.elasticsearch.logging.LogManager; @@ -46,6 +48,7 @@ public class MicrosoftGraphAuthzRealm extends Realm { private final HttpClient httpClient; private final RealmConfig config; private final UserRoleMapper roleMapper; + private final SecureString clientSecret; public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { super(config); @@ -53,6 +56,7 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { this.roleMapper = roleMapper; this.config = config; this.httpClient = HttpClients.createDefault(); + this.clientSecret = config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); } @Override @@ -110,7 +114,7 @@ private String fetchAccessToken() throws IOException, ParseException { new BasicNameValuePair("grant_type", "client_credentials"), new BasicNameValuePair("scope", "https://graph.microsoft.com/.default"), new BasicNameValuePair("client_id", config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_ID)), - new BasicNameValuePair("client_secret", config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET)) + new BasicNameValuePair("client_secret", clientSecret.toString()) ) ) ); diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java index e2948a757eeb5..ca9c8b73a5456 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java @@ -9,6 +9,8 @@ package org.elasticsearch.xpack.security.authz.microsoft; +import org.elasticsearch.common.settings.SecureSetting; +import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.xpack.core.security.authc.RealmSettings; @@ -24,10 +26,9 @@ public class MicrosoftGraphAuthzRealmSettings { Setting.Property.NodeScope ); - public static final Setting.AffixSetting CLIENT_SECRET = RealmSettings.simpleString( + public static final Setting.AffixSetting CLIENT_SECRET = RealmSettings.secureString( REALM_TYPE, - "client_secret", - Setting.Property.NodeScope + "client_secret" ); public static final Setting.AffixSetting TENANT_ID = RealmSettings.simpleString( diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle index bd8f02069f184..fa365e3ad36b8 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle @@ -5,8 +5,5 @@ dependencies { javaRestTestImplementation project(':x-pack:plugin:security') javaRestTestImplementation testArtifact(project(":x-pack:plugin:security:qa:saml-rest-tests"), "javaRestTest") clusterPlugins project(':plugins:microsoft-graph-authz') -} - -tasks.named('javaRestTest') { - usesDefaultDistribution("Reason why default distribution is required") + clusterModules project(":modules:analysis-common") } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index 6abc2f585012b..c80be130b5d1a 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -68,7 +68,7 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { private static ElasticsearchCluster initTestCluster() { return ElasticsearchCluster.local() - .distribution(DistributionType.DEFAULT) + .module("analysis-common") .setting("xpack.security.enabled", "true") .setting("xpack.license.self_generated.type", "trial") .setting("xpack.security.authc.token.enabled", "true") @@ -88,7 +88,7 @@ private static ElasticsearchCluster initTestCluster() { .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) - .setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.client_secret", CLIENT_SECRET) + .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) .setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.access_token_host", graphFixture::getBaseUrl) From 34b6276cc4dafbc4c6e0e37ddccee2aec30b4cc3 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Tue, 20 May 2025 15:49:20 +0100 Subject: [PATCH 04/66] use JSONObjectUtils for parsing json --- .../microsoft/MicrosoftGraphAuthzRealm.java | 16 +++++++++------- .../MicrosoftGraphAuthzRealmSettings.java | 1 - 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 6379073a5dfec..f2e906f68562c 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -41,6 +41,9 @@ import java.util.List; import java.util.Map; +import static com.nimbusds.jose.util.JSONObjectUtils.getJSONObjectArray; +import static com.nimbusds.jose.util.JSONObjectUtils.getString; + public class MicrosoftGraphAuthzRealm extends Realm { private static final Logger logger = LogManager.getLogger(MicrosoftGraphAuthzRealm.class); @@ -122,7 +125,7 @@ private String fetchAccessToken() throws IOException, ParseException { final var response = httpClient.execute(request, new BasicResponseHandler()); final var json = JSONObjectUtils.parse(response); - final var token = (String) json.get("access_token"); + final var token = getString(json, "access_token"); logger.trace("Azure access token [{}]", token); return token; @@ -141,8 +144,8 @@ private Tuple fetchUserProperties(String userId, String token) t final var response = httpClient.execute(request, new BasicResponseHandler()); final var json = JSONObjectUtils.parse(response); - final var email = (String) json.get("mail"); - final var name = (String) json.get("displayName"); + final var email = getString(json, "email"); + final var name = getString(json, "displayName"); logger.trace("User [{}] has email [{}]", name, email); @@ -166,10 +169,9 @@ private List fetchGroupMembership(String userId, String token) throws IO final var response = httpClient.execute(request, new BasicResponseHandler()); var json = JSONObjectUtils.parse(response); - nextPage = (String) json.get("@odata.nextLink"); - for (var groupData : (List) json.get("value")) { - final var properties = (Map) groupData; - groups.add((String) properties.get("id")); + nextPage = getString(json, "@odata.nextLink"); + for (var groupData : getJSONObjectArray(json, "groups")) { + groups.add(getString(groupData, "id")); } } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java index ca9c8b73a5456..11ccf5be620c3 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java @@ -9,7 +9,6 @@ package org.elasticsearch.xpack.security.authz.microsoft; -import org.elasticsearch.common.settings.SecureSetting; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.xpack.core.security.authc.RealmSettings; From 11cd43a84e26d24ee24d79dbe867165142abcce9 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Wed, 21 May 2025 11:32:10 +0100 Subject: [PATCH 05/66] (not working) attempt to use official graph SDK --- .../gradle/util/GradleUtils.java | 4 +- gradle/verification-metadata.xml | 414 ++++++++++++++++++ libs/kiota-merged/build.gradle | 14 + plugins/microsoft-graph-authz/build.gradle | 26 +- .../licenses/nimbus-jose-jwt-LICENSE.txt | 202 --------- .../licenses/nimbus-jose-jwt-NOTICE.txt | 14 - .../src/main/java/module-info.java | 4 + .../microsoft/MicrosoftGraphAuthzRealm.java | 59 ++- .../MicrosoftGraphAuthzPluginIT.java | 2 +- 9 files changed, 516 insertions(+), 223 deletions(-) create mode 100644 libs/kiota-merged/build.gradle delete mode 100644 plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-LICENSE.txt delete mode 100644 plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-NOTICE.txt diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/util/GradleUtils.java b/build-tools/src/main/java/org/elasticsearch/gradle/util/GradleUtils.java index 8068a8fd9801f..f293e41ba882d 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/util/GradleUtils.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/util/GradleUtils.java @@ -199,7 +199,9 @@ public static void disableTransitiveDependencies(Configuration config) { config.getDependencies().all(dep -> { if (dep instanceof ModuleDependency && dep instanceof ProjectDependency == false - && dep.getGroup().startsWith("org.elasticsearch") == false) { + && dep.getGroup().startsWith("org.elasticsearch") == false + && dep.getGroup().startsWith("com.squareup") == false + && dep.getGroup().startsWith("org.jetbrains.kotlin") == false) { ((ModuleDependency) dep).setTransitive(false); } }); diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 566a640e9bd55..b47ba7c5d3a77 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -96,6 +96,11 @@ + + + + + @@ -106,11 +111,21 @@ + + + + + + + + + + @@ -136,6 +151,11 @@ + + + + + @@ -641,6 +661,11 @@ + + + + + @@ -973,11 +998,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1068,6 +1123,11 @@ + + + + + @@ -1083,6 +1143,11 @@ + + + + + @@ -1563,6 +1628,11 @@ + + + + + @@ -1573,6 +1643,11 @@ + + + + + @@ -1593,6 +1668,11 @@ + + + + + @@ -1756,6 +1836,11 @@ + + + + + @@ -3986,26 +4071,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/kiota-merged/build.gradle b/libs/kiota-merged/build.gradle new file mode 100644 index 0000000000000..47d380bfea70d --- /dev/null +++ b/libs/kiota-merged/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'elasticsearch.build' +apply plugin: 'com.gradleup.shadow' + +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" +} + +tasks.named('shadowJar').configure { + manifest { + attributes 'Automatic-Module-Name': 'com.microsoft.kiota' + } +} diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 260b99e019f96..c9e19f7c87496 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -7,7 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -apply plugin: "elasticsearch.internal-java-rest-test" +plugins { + id "elasticsearch.internal-java-rest-test" +} esplugin { name = "microsoft-graph-authz" @@ -19,4 +21,26 @@ esplugin { dependencies { compileOnly project(":x-pack:plugin:core") compileOnly "com.nimbusds:nimbus-jose-jwt:10.0.2" + + implementation "com.microsoft.graph:microsoft-graph:6.36.0" + implementation "com.microsoft.graph:microsoft-graph-core:3.6.1" + implementation project(path: ":libs: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 "com.squareup.okhttp3:okhttp:4.11.0" } diff --git a/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-LICENSE.txt deleted file mode 100644 index d645695673349..0000000000000 --- a/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - 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. diff --git a/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-NOTICE.txt deleted file mode 100644 index cb9ad94f662a6..0000000000000 --- a/plugins/microsoft-graph-authz/licenses/nimbus-jose-jwt-NOTICE.txt +++ /dev/null @@ -1,14 +0,0 @@ -Nimbus JOSE + JWT - -Copyright 2012 - 2018, Connect2id Ltd. - -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. diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/plugins/microsoft-graph-authz/src/main/java/module-info.java index a59188bbe1341..e7b1a05d915a4 100644 --- a/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ b/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -17,6 +17,10 @@ requires org.apache.httpcomponents.httpclient; requires org.apache.httpcomponents.httpcore; requires com.nimbusds.jose.jwt; + requires com.microsoft.kiota; + requires com.microsoft.graph; + requires com.azure.identity; + requires com.microsoft.graph.core; provides org.elasticsearch.xpack.core.security.SecurityExtension with MicrosoftGraphAuthzPlugin; } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index f2e906f68562c..82a9e680a1411 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -9,6 +9,11 @@ package org.elasticsearch.xpack.security.authz.microsoft; +import com.azure.identity.ClientSecretCredentialBuilder; +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.nimbusds.jose.util.JSONObjectUtils; import org.apache.http.client.HttpClient; @@ -21,7 +26,6 @@ import org.elasticsearch.action.ActionListener; 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.Tuple; import org.elasticsearch.logging.LogManager; @@ -80,9 +84,12 @@ public void authenticate(AuthenticationToken token, ActionListener listener) { try { - final var token = fetchAccessToken(); - final var userProperties = fetchUserProperties(principal, token); - final var groups = fetchGroupMembership(principal, token); +// final var token = fetchAccessToken(); +// final var userProperties = fetchUserProperties(principal, token); +// final var groups = fetchGroupMembership(principal, token); + final var client = buildClient(); + final var userProperties = sdkFetchUserProperties(client, principal); + final var groups = sdkFetchGroupMembership(client, principal); final var userData = new UserRoleMapper.UserData(principal, null, groups, Map.of(), config); @@ -131,6 +138,14 @@ private String fetchAccessToken() throws IOException, ParseException { return token; } + private GraphServiceClient buildClient() { + final var credentialProvider = new ClientSecretCredentialBuilder().clientId( + config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_ID) + ).clientSecret(clientSecret.toString()).tenantId(config.getSetting(MicrosoftGraphAuthzRealmSettings.TENANT_ID)).build(); + + return new GraphServiceClient(credentialProvider, "https://graph.microsoft.com/.default"); + } + private Tuple fetchUserProperties(String userId, String token) throws IOException, ParseException { var request = new HttpGet( Strings.format( @@ -152,6 +167,15 @@ private Tuple fetchUserProperties(String userId, String token) t return Tuple.tuple(name, email); } + private Tuple sdkFetchUserProperties(GraphServiceClient client, String userId) { + var response = client.usersWithUserPrincipalName(userId) + .get(requestConfig -> requestConfig.queryParameters.select = new String[] { "displayName", "email" }); + + logger.trace("User [{}] has email [{}]", response.getDisplayName(), response.getMail()); + + return Tuple.tuple(response.getDisplayName(), response.getMail()); + } + private List fetchGroupMembership(String userId, String token) throws IOException, ParseException, URISyntaxException { var request = new HttpGet(); request.addHeader("Authorization", "Bearer " + token); @@ -180,4 +204,31 @@ private List fetchGroupMembership(String userId, String token) throws IO return groups; } + private List sdkFetchGroupMembership(GraphServiceClient client, String userId) throws ReflectiveOperationException { + List groups = new ArrayList<>(); + + var groupMembership = client.users().byUserId(userId).memberOf().graphGroup().get(requestConfig -> { + requestConfig.queryParameters.select = new String[] { "id" }; + requestConfig.queryParameters.top = 999; + }); + + var pageIterator = new PageIterator.Builder() + .client(client) + .collectionPage(groupMembership) + .collectionPageFactory(GroupCollectionResponse::createFromDiscriminatorValue) + .requestConfigurator(requestInfo -> { + requestInfo.addQueryParameter("%24select", new String[] { "id" }); + requestInfo.addQueryParameter("%24top", "999"); + return requestInfo; + }) + .processPageItemCallback(group -> { + groups.add(group.getId()); + return true; + }) + .build(); + + pageIterator.iterate(); + + return groups; + } } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index c80be130b5d1a..db6a5703d5cd3 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -45,7 +45,7 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { - private static final String TENANT_ID = "tenant_id"; + 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"; From aae596edc314edfaa6623551e9d1b744661f12df Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 22 May 2025 17:01:09 +0100 Subject: [PATCH 06/66] WIP at least it runs; if only it didn't mysteriously hang --- .../azurecore/AzureCoreClassPatcher.java | 85 +++++++++++++++++++ .../patches/azurecore/ImplUtilsPatcher.java | 32 +++++++ .../patches/hdfs/MethodReplacement.java | 2 +- .../gradle/util/GradleUtils.java | 4 +- gradle/verification-metadata.xml | 40 +++++++++ libs/kiota-merged/build.gradle | 4 + plugins/microsoft-graph-authz/build.gradle | 43 ++++++++++ .../src/main/java/module-info.java | 1 + .../microsoft/MicrosoftGraphAuthzRealm.java | 28 ++++-- .../plugin-metadata/entitlement-policy.yaml | 5 ++ .../microsoft/AzureGraphHttpFixture.java | 33 ++++++- .../MicrosoftGraphAuthzPluginIT.java | 8 +- .../javaRestTest/resources/server/cert.key | 28 ++++++ .../javaRestTest/resources/server/cert.pem | 20 +++++ 14 files changed, 318 insertions(+), 15 deletions(-) create mode 100644 build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java create mode 100644 build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java create mode 100644 plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml create mode 100644 x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.key create mode 100644 x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.pem diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java new file mode 100644 index 0000000000000..db569935a38e2 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java @@ -0,0 +1,85 @@ +/* + * 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.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.io.FileOutputStream; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +import static org.elasticsearch.gradle.internal.dependencies.patches.PatcherInfo.classPatcher; + +public abstract class AzureCoreClassPatcher implements TransformAction { + + private static final String JAR_FILE_TO_PATCH = "azure-core-1.55.3.jar"; + + private static final List CLASS_PATCHERS = List.of( + classPatcher( + "com/azure/core/implementation/ImplUtils.class", + "7beda5bdff5ea460cfc08721a188cf07d16e0c987dae45401fca7abf4e6e6c0e", + ImplUtilsPatcher::new + ) + ); + + @Classpath + @InputArtifact + public abstract Provider getInputArtifact(); + + @Override + public void transform(@NotNull TransformOutputs outputs) { + File inputFile = getInputArtifact().get().getAsFile(); + + if (inputFile.getName().equals(JAR_FILE_TO_PATCH)) { + System.out.println("Patching " + inputFile.getName()); + File firstOutputFile = outputs.file(inputFile.getName().replace(".jar", "-half-patched")); + + try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(firstOutputFile))) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String entryName = entry.getName(); + + if (entryName.endsWith("MANIFEST.MF") == false) { + jos.putNextEntry(new JarEntry(entryName)); + try (InputStream is = jarFile.getInputStream(entry)) { + is.transferTo(jos); + } + jos.closeEntry(); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + File secondOutputFile = outputs.file(inputFile.getName().replace(".jar", "-patched.jar")); + Utils.patchJar(firstOutputFile, secondOutputFile, CLASS_PATCHERS); + } else { + System.out.println("Skipping " + inputFile.getName()); + outputs.file(getInputArtifact()); + } + } + + +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java new file mode 100644 index 0000000000000..199597665ee66 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 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.hdfs.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); + if (name.equals("addShutdownHookSafely")) { + return new MethodReplacement(mv, () -> { + mv.visitInsn(Opcodes.ACONST_NULL); + mv.visitInsn(Opcodes.ARETURN); + }); + } + return mv; + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/MethodReplacement.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/MethodReplacement.java index 7bc6a6c0d530f..0d6af88b0111e 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/MethodReplacement.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/MethodReplacement.java @@ -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; diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/util/GradleUtils.java b/build-tools/src/main/java/org/elasticsearch/gradle/util/GradleUtils.java index f293e41ba882d..8068a8fd9801f 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/util/GradleUtils.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/util/GradleUtils.java @@ -199,9 +199,7 @@ public static void disableTransitiveDependencies(Configuration config) { config.getDependencies().all(dep -> { if (dep instanceof ModuleDependency && dep instanceof ProjectDependency == false - && dep.getGroup().startsWith("org.elasticsearch") == false - && dep.getGroup().startsWith("com.squareup") == false - && dep.getGroup().startsWith("org.jetbrains.kotlin") == false) { + && dep.getGroup().startsWith("org.elasticsearch") == false) { ((ModuleDependency) dep).setTransitive(false); } }); diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index b47ba7c5d3a77..ab7af8f883808 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -106,6 +106,11 @@ + + + + + @@ -1033,6 +1038,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -1093,11 +1118,21 @@ + + + + + + + + + + @@ -1493,6 +1528,11 @@ + + + + + diff --git a/libs/kiota-merged/build.gradle b/libs/kiota-merged/build.gradle index 47d380bfea70d..edd4753ff83c6 100644 --- a/libs/kiota-merged/build.gradle +++ b/libs/kiota-merged/build.gradle @@ -5,6 +5,10 @@ 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 { diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index c9e19f7c87496..3c37e93fc92d7 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -18,6 +18,31 @@ esplugin { 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") compileOnly "com.nimbusds:nimbus-jose-jwt:10.0.2" @@ -42,5 +67,23 @@ dependencies { 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.2.0" + runtimeOnly "com.squareup.okio:okio-jvm:3.2.0" + runtimeOnly "io.github.std-uritemplate:std-uritemplate:2.0.0" + runtimeOnly "com.azure:azure-core-http-okhttp:1.12.10" + compileOnly "com.nimbusds:oauth2-oidc-sdk:11.22.2" + runtimeOnly "com.nimbusds:content-type:2.3" + + 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) + } } diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/plugins/microsoft-graph-authz/src/main/java/module-info.java index e7b1a05d915a4..3abc87fd2de16 100644 --- a/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ b/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -21,6 +21,7 @@ requires com.microsoft.graph; requires com.azure.identity; requires com.microsoft.graph.core; + requires kotlin.stdlib; provides org.elasticsearch.xpack.core.security.SecurityExtension with MicrosoftGraphAuthzPlugin; } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 82a9e680a1411..2a830425ad91d 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -10,10 +10,12 @@ package org.elasticsearch.xpack.security.authz.microsoft; 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.nimbusds.jose.util.JSONObjectUtils; import org.apache.http.client.HttpClient; @@ -64,6 +66,9 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { this.config = config; this.httpClient = HttpClients.createDefault(); this.clientSecret = config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); + + kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(clientSecret, "clientSecret"); + // TODO license check } @Override @@ -84,9 +89,9 @@ public void authenticate(AuthenticationToken token, ActionListener listener) { try { -// final var token = fetchAccessToken(); -// final var userProperties = fetchUserProperties(principal, token); -// final var groups = fetchGroupMembership(principal, token); + // final var token = fetchAccessToken(); + // final var userProperties = fetchUserProperties(principal, token); + // final var groups = fetchGroupMembership(principal, token); final var client = buildClient(); final var userProperties = sdkFetchUserProperties(client, principal); final var groups = sdkFetchGroupMembership(client, principal); @@ -139,11 +144,21 @@ private String fetchAccessToken() throws IOException, ParseException { } private GraphServiceClient buildClient() { + logger.trace("building client"); final var credentialProvider = new ClientSecretCredentialBuilder().clientId( config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_ID) - ).clientSecret(clientSecret.toString()).tenantId(config.getSetting(MicrosoftGraphAuthzRealmSettings.TENANT_ID)).build(); + ) + .clientSecret(clientSecret.toString()) + .tenantId(config.getSetting(MicrosoftGraphAuthzRealmSettings.TENANT_ID)) + .authorityHost(config.getSetting(MicrosoftGraphAuthzRealmSettings.ACCESS_TOKEN_HOST)) + .build(); - return new GraphServiceClient(credentialProvider, "https://graph.microsoft.com/.default"); + return new GraphServiceClient( + new BaseGraphRequestAdapter( + new AzureIdentityAuthenticationProvider(credentialProvider, Strings.EMPTY_ARRAY, "https://graph.microsoft.com/.default"), + config.getSetting(MicrosoftGraphAuthzRealmSettings.API_HOST) + ) + ); } private Tuple fetchUserProperties(String userId, String token) throws IOException, ParseException { @@ -212,8 +227,7 @@ private List sdkFetchGroupMembership(GraphServiceClient client, String u requestConfig.queryParameters.top = 999; }); - var pageIterator = new PageIterator.Builder() - .client(client) + var pageIterator = new PageIterator.Builder().client(client) .collectionPage(groupMembership) .collectionPageFactory(GroupCollectionResponse::createFromDiscriminatorValue) .requestConfigurator(requestInfo -> { diff --git a/plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml b/plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml new file mode 100644 index 0000000000000..b05599c660eb2 --- /dev/null +++ b/plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml @@ -0,0 +1,5 @@ +okhttp3: + - outbound_network + - manage_threads +okio: + - manage_threads diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java index c591472ec2a68..7fdb991d01b96 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java @@ -10,10 +10,15 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; +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; @@ -21,12 +26,19 @@ import org.elasticsearch.xcontent.XContentType; import org.junit.rules.ExternalResource; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; + 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; @@ -41,7 +53,7 @@ public class AzureGraphHttpFixture extends ExternalResource { private final String displayName; private final String email; - private HttpServer server; + private HttpsServer server; public AzureGraphHttpFixture( String tenantId, @@ -64,7 +76,18 @@ protected void before() throws Throwable { final var jwt = "test jwt"; final var skipToken = UUID.randomUUID().toString(); - server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); + 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)); + server.createContext("/" + tenantId + "/oauth2/v2.0/token", exchange -> { if (exchange.getRequestMethod().equals("POST") == false) { httpError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected POST request"); @@ -183,6 +206,10 @@ protected void before() throws Throwable { exchange.close(); }); + server.createContext("/", exchange -> { + logger.warn("Unhandled request for [{}]", exchange.getRequestURI()); + exchange.close(); + }); server.start(); } @@ -192,7 +219,7 @@ protected void after() { } public String getBaseUrl() { - return "http://" + server.getAddress().getHostString() + ":" + server.getAddress().getPort(); + return "https://" + server.getAddress().getHostString() + ":" + server.getAddress().getPort(); } private void httpError(HttpExchange exchange, RestStatus statusCode, String message) throws IOException { diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index db6a5703d5cd3..438a61d7e8875 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -13,6 +13,7 @@ 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.distribution.DistributionType; @@ -61,8 +62,11 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { 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(graphFixture).around(cluster); + public static TestRule ruleChain = RuleChain.outerRule(graphFixture).around(trustStore).around(cluster); private static final String IDP_ENTITY_ID = "http://idp.example.org/"; @@ -93,6 +97,8 @@ private static ElasticsearchCluster initTestCluster() { .setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.graph_host", graphFixture::getBaseUrl) .setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.access_token_host", graphFixture::getBaseUrl) .setting("logger.org.elasticsearch.xpack.security.authz.microsoft", "TRACE") + .systemProperty("javax.net.ssl.trustStore", () -> trustStore.getTrustStorePath().toString()) + .systemProperty("javax.net.ssl.trustStoreType", "jks") .build(); } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.key b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.key new file mode 100644 index 0000000000000..29efd3a6913a0 --- /dev/null +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.key @@ -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----- diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.pem b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.pem new file mode 100644 index 0000000000000..b291aaa9362de --- /dev/null +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.pem @@ -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----- From 5c337b2317cf774237a9090a1a754876a209f078 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 22 May 2025 17:26:50 +0100 Subject: [PATCH 07/66] WIP unit tests (for debugging the plugin) --- plugins/microsoft-graph-authz/build.gradle | 2 + .../microsoft/MicrosoftGraphAuthzRealm.java | 37 +++++++- .../MicrosoftGraphAuthzRealmTests.java | 93 +++++++++++++++++++ 3 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 3c37e93fc92d7..a2791f6c9126a 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -76,6 +76,8 @@ dependencies { compileOnly "com.nimbusds:oauth2-oidc-sdk:11.22.2" runtimeOnly "com.nimbusds:content-type:2.3" + testRuntimeOnly "net.minidev:json-smart:2.5.2" + attributesSchema { attribute(patched) } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 2a830425ad91d..41fe8c329a7f6 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -28,6 +28,8 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.SettingsException; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.core.Tuple; import org.elasticsearch.logging.LogManager; @@ -36,6 +38,7 @@ 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.user.User; @@ -54,23 +57,45 @@ public class MicrosoftGraphAuthzRealm extends Realm { private static final Logger logger = LogManager.getLogger(MicrosoftGraphAuthzRealm.class); - private final HttpClient httpClient; private final RealmConfig config; private final UserRoleMapper roleMapper; + private final HttpClient httpClient; private final SecureString clientSecret; public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { + this(roleMapper, config, HttpClients.createDefault()); + } + + /* package-private for testing */ MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config, HttpClient httpClient) { super(config); - this.roleMapper = roleMapper; this.config = config; - this.httpClient = HttpClients.createDefault(); + this.roleMapper = roleMapper; + this.httpClient = httpClient; this.clientSecret = config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); + require(MicrosoftGraphAuthzRealmSettings.CLIENT_ID); + require(MicrosoftGraphAuthzRealmSettings.TENANT_ID); + + if (clientSecret.isEmpty()) { + throw new SettingsException( + "The configuration setting [" + + RealmSettings.getFullSettingKey(config, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET) + + "] is required" + ); + } + kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(clientSecret, "clientSecret"); // TODO license check } + private void require(Setting.AffixSetting 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; @@ -107,7 +132,7 @@ public void lookupUser(String principal, ActionListener listener) { Map.of(), true ); - logger.debug("Entra ID user {}", user); + logger.trace("Entra ID user {}", user); l.onResponse(user); })); } catch (Exception e) { @@ -195,6 +220,8 @@ private List fetchGroupMembership(String userId, String token) throws IO var request = new HttpGet(); request.addHeader("Authorization", "Bearer " + token); + // TODO need to think about transient group membership + // also do we need to care about other memberships, e.g. Directory Roles and "Administrative Units"? var nextPage = Strings.format( "%s/v1.0/users/%s/memberOf/microsoft.graph.group?$select=id&$top=999", config.getSetting(MicrosoftGraphAuthzRealmSettings.API_HOST), @@ -209,7 +236,7 @@ private List fetchGroupMembership(String userId, String token) throws IO var json = JSONObjectUtils.parse(response); nextPage = getString(json, "@odata.nextLink"); - for (var groupData : getJSONObjectArray(json, "groups")) { + for (var groupData : getJSONObjectArray(json, "value")) { groups.add(getString(groupData, "id")); } } diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java new file mode 100644 index 0000000000000..f98147814bc7b --- /dev/null +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -0,0 +1,93 @@ +/* + * 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.apache.http.client.HttpClient; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.settings.MockSecureSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.env.Environment; +import org.elasticsearch.env.TestEnvironment; +import org.elasticsearch.test.ESTestCase; +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.Before; + +import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.mock; + +public class MicrosoftGraphAuthzRealmTests extends ESTestCase { + + private Settings globalSettings; + private Environment env; + private ThreadContext threadContext; + + @Before + public void setup() { + globalSettings = Settings.builder().put("path.home", createTempDir()).build(); + env = TestEnvironment.newEnvironment(globalSettings); + threadContext = new ThreadContext(globalSettings); + } + + public void testLookupUser() { + final var realmName = "ms-graph"; + final var realmId = new RealmConfig.RealmIdentifier(MicrosoftGraphAuthzRealmSettings.REALM_TYPE, realmName); + + final var roleMapper = mock(UserRoleMapper.class); + final var secureSettings = new MockSecureSettings(); + secureSettings.setString(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET), "client-secret"); + final var realmSettings = Settings.builder() + .put(globalSettings) + .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) + .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_ID), "client-id") + .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.TENANT_ID), "tenant-id") + .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.ACCESS_TOKEN_HOST), "https://localhost:12345") + .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.API_HOST), "https://localhost:12345/v1.0") + .setSecureSettings(secureSettings) + .build(); + + final var config = new RealmConfig( + realmId, + realmSettings, + env, + threadContext + ); + final var client = mock(HttpClient.class); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client); + final var future = new PlainActionFuture(); + realm.lookupUser("principal", future); + final var user = future.actionGet(); + assertThat(user.principal(), equalTo("principal")); + assertThat(user.email(), equalTo("email")); + assertThat(user.roles(), arrayContaining("role1")); + } + + public void testHandleGetAccessTokenError() { + fail(); + } + + public void testHandleGetUserPropertiesError() { + fail(); + } + + public void testHandleGetGroupMembershipError() { + fail(); + } + + public void testGroupMembershipPagination() { + fail(); + } +} From 108f46233fe318d695be358da4f6749f07d158b8 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Fri, 23 May 2025 11:56:36 +0100 Subject: [PATCH 08/66] WIP at least it works against real azure --- plugins/microsoft-graph-authz/build.gradle | 5 +- .../licenses/azure-LICENSE.txt | 21 ++ .../licenses/gson-LICENSE | 202 ++++++++++++++++++ .../licenses/jackson-LICENSE | 8 + .../licenses/jackson-NOTICE | 20 ++ .../licenses/jna-LICENSE.txt | 177 +++++++++++++++ .../licenses/jna-NOTICE.txt | 1 + .../licenses/kotlin-LICENSE.txt | 202 ++++++++++++++++++ .../licenses/kotlin-NOTICE.txt | 8 + .../licenses/microsoft-graph-LICENSE.txt | 21 ++ .../licenses/msal4j-LICENSE.txt | 21 ++ .../licenses/msal4j-NOTICE.txt | 0 .../licenses/nimbus-LICENSE.txt | 202 ++++++++++++++++++ .../licenses/okhttp-LICENSE.txt | 202 ++++++++++++++++++ .../licenses/okio-LICENSE.txt | 202 ++++++++++++++++++ .../licenses/opentelemetry-LICENSE.txt | 201 +++++++++++++++++ .../licenses/opentelemetry-NOTICE.txt | 0 .../licenses/reactive-streams-LICENSE.txt | 7 + .../licenses/reactive-streams-NOTICE.txt | 0 .../licenses/reactor-core-LICENSE.txt | 202 ++++++++++++++++++ .../licenses/reactor-core-NOTICE.txt | 0 .../licenses/std-uritemplate-LICENSE.txt | 201 +++++++++++++++++ .../src/main/java/module-info.java | 1 + .../microsoft/MicrosoftGraphAuthzRealm.java | 4 +- .../MicrosoftGraphAuthzRealmSettings.java | 7 +- x-pack/plugin/security/build.gradle | 1 + 26 files changed, 1910 insertions(+), 6 deletions(-) create mode 100644 plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/gson-LICENSE create mode 100644 plugins/microsoft-graph-authz/licenses/jackson-LICENSE create mode 100644 plugins/microsoft-graph-authz/licenses/jackson-NOTICE create mode 100644 plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/msal4j-NOTICE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/nimbus-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/opentelemetry-NOTICE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/reactive-streams-NOTICE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/reactor-core-NOTICE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index a2791f6c9126a..ea3bcc033d12b 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -73,8 +73,7 @@ dependencies { runtimeOnly "com.squareup.okio:okio-jvm:3.2.0" runtimeOnly "io.github.std-uritemplate:std-uritemplate:2.0.0" runtimeOnly "com.azure:azure-core-http-okhttp:1.12.10" - compileOnly "com.nimbusds:oauth2-oidc-sdk:11.22.2" - runtimeOnly "com.nimbusds:content-type:2.3" + implementation "com.google.code.gson:gson:2.10" testRuntimeOnly "net.minidev:json-smart:2.5.2" @@ -89,3 +88,5 @@ dependencies { to.attribute(patched, true) } } + +tasks.named("javadoc").configure { enabled = false } diff --git a/plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt new file mode 100644 index 0000000000000..3423e9584d153 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt @@ -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. diff --git a/plugins/microsoft-graph-authz/licenses/gson-LICENSE b/plugins/microsoft-graph-authz/licenses/gson-LICENSE new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/gson-LICENSE @@ -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. diff --git a/plugins/microsoft-graph-authz/licenses/jackson-LICENSE b/plugins/microsoft-graph-authz/licenses/jackson-LICENSE new file mode 100644 index 0000000000000..f5f45d26a49d6 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/jackson-LICENSE @@ -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 diff --git a/plugins/microsoft-graph-authz/licenses/jackson-NOTICE b/plugins/microsoft-graph-authz/licenses/jackson-NOTICE new file mode 100644 index 0000000000000..4c976b7b4cc58 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/jackson-NOTICE @@ -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. diff --git a/plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt new file mode 100644 index 0000000000000..f433b1a53f5b8 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt @@ -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 diff --git a/plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt new file mode 100644 index 0000000000000..8d1c8b69c3fce --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt @@ -0,0 +1 @@ + diff --git a/plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt @@ -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. diff --git a/plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt new file mode 100644 index 0000000000000..80dbbefa483f6 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt @@ -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 diff --git a/plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt new file mode 100644 index 0000000000000..fce1f3f63ef42 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt @@ -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. diff --git a/plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt new file mode 100644 index 0000000000000..21071075c2459 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt @@ -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 diff --git a/plugins/microsoft-graph-authz/licenses/msal4j-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/msal4j-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/plugins/microsoft-graph-authz/licenses/nimbus-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/nimbus-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/nimbus-LICENSE.txt @@ -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. diff --git a/plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt @@ -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. diff --git a/plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt @@ -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. diff --git a/plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt new file mode 100644 index 0000000000000..261eeb9e9f8b2 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt @@ -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. diff --git a/plugins/microsoft-graph-authz/licenses/opentelemetry-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/opentelemetry-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt new file mode 100644 index 0000000000000..1e141c13ddba2 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt @@ -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. diff --git a/plugins/microsoft-graph-authz/licenses/reactive-streams-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/reactive-streams-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt new file mode 100644 index 0000000000000..d5dd862b1759b --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt @@ -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. + diff --git a/plugins/microsoft-graph-authz/licenses/reactor-core-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/reactor-core-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt new file mode 100644 index 0000000000000..c5127d773bd88 --- /dev/null +++ b/plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt @@ -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. diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/plugins/microsoft-graph-authz/src/main/java/module-info.java index 3abc87fd2de16..db35ba20ec3f7 100644 --- a/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ b/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -22,6 +22,7 @@ requires com.azure.identity; requires com.microsoft.graph.core; requires kotlin.stdlib; + requires com.google.gson; provides org.elasticsearch.xpack.core.security.SecurityExtension with MicrosoftGraphAuthzPlugin; } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 41fe8c329a7f6..73aa6b258fd34 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -60,7 +60,7 @@ public class MicrosoftGraphAuthzRealm extends Realm { private final RealmConfig config; private final UserRoleMapper roleMapper; private final HttpClient httpClient; - private final SecureString clientSecret; + private final String clientSecret; public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { this(roleMapper, config, HttpClients.createDefault()); @@ -85,6 +85,8 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { ); } + logger.trace("pls {}", new com.google.gson.JsonParser()); + kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(clientSecret, "clientSecret"); // TODO license check } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java index 11ccf5be620c3..dac664874cdea 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java @@ -25,9 +25,10 @@ public class MicrosoftGraphAuthzRealmSettings { Setting.Property.NodeScope ); - public static final Setting.AffixSetting CLIENT_SECRET = RealmSettings.secureString( + public static final Setting.AffixSetting CLIENT_SECRET = RealmSettings.simpleString( REALM_TYPE, - "client_secret" + "client_secret", + Setting.Property.NodeScope ); public static final Setting.AffixSetting TENANT_ID = RealmSettings.simpleString( @@ -45,7 +46,7 @@ public class MicrosoftGraphAuthzRealmSettings { public static final Setting.AffixSetting API_HOST = Setting.affixKeySetting( RealmSettings.realmSettingPrefix(REALM_TYPE), "graph_host", - key -> Setting.simpleString(key, "https://graph.microsoft.com", Setting.Property.NodeScope) + key -> Setting.simpleString(key, "https://graph.microsoft.com/v1.0", Setting.Property.NodeScope) ); public static List> getSettings() { diff --git a/x-pack/plugin/security/build.gradle b/x-pack/plugin/security/build.gradle index fb798e25f1a34..940185dd952f4 100644 --- a/x-pack/plugin/security/build.gradle +++ b/x-pack/plugin/security/build.gradle @@ -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) { /* From c39c3f62c9c40fa4585d9b85d59db8b56a17e5a3 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Fri, 23 May 2025 14:25:43 +0100 Subject: [PATCH 09/66] document assorted hacks required to get this thing working --- .../azurecore/AzureCoreClassPatcher.java | 7 + .../patches/azurecore/ImplUtilsPatcher.java | 3 + gradle/verification-metadata.xml | 344 ------------------ plugins/microsoft-graph-authz/build.gradle | 3 +- .../kiota-merged/build.gradle | 11 + .../kiota-merged/licenses/kiota-LICENSE | 21 ++ .../src/main/java/module-info.java | 1 - .../microsoft/MicrosoftGraphAuthzRealm.java | 104 +----- .../MicrosoftGraphAuthzRealmTests.java | 4 +- 9 files changed, 45 insertions(+), 453 deletions(-) rename {libs => plugins/microsoft-graph-authz}/kiota-merged/build.gradle (52%) create mode 100644 plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java index db569935a38e2..9ef828084278f 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java @@ -53,6 +53,13 @@ public void transform(@NotNull TransformOutputs outputs) { if (inputFile.getName().equals(JAR_FILE_TO_PATCH)) { System.out.println("Patching " + inputFile.getName()); + // This would be cleaner if gradle artifact transformers supported temp files https://github.com/gradle/gradle/issues/30440. + // We need to patch (or delete) the Manifest file as it contains signatures for the class files, which obviously get + // invalidated when we patch the bytecode, but the existing patch tools only work on class files, so we unfortunately need to + // duplicate the loop here. We _also_ need to create a "temporary" jar to write (or not) the patched manifest and the rest of + // the jar contents. + // TODO we could possibly update `Utils.patchJar` to enable patching manifests etc and most of this would be significantly + // simplified File firstOutputFile = outputs.file(inputFile.getName().replace(".jar", "-half-patched")); try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(firstOutputFile))) { diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java index 199597665ee66..a552804b3b62f 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java @@ -21,6 +21,9 @@ class ImplUtilsPatcher extends 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 strictly forbidden in ES (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); diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index ab7af8f883808..cee1bdeabe0a4 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -666,11 +666,6 @@ - - - - - @@ -1118,21 +1113,11 @@ - - - - - - - - - - @@ -1876,11 +1861,6 @@ - - - - - @@ -4111,355 +4091,31 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index ea3bcc033d12b..34db67bab567f 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -45,11 +45,10 @@ configurations { dependencies { compileOnly project(":x-pack:plugin:core") - compileOnly "com.nimbusds:nimbus-jose-jwt:10.0.2" implementation "com.microsoft.graph:microsoft-graph:6.36.0" implementation "com.microsoft.graph:microsoft-graph-core:3.6.1" - implementation project(path: ":libs:kiota-merged", configuration: 'shadow') + 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" diff --git a/libs/kiota-merged/build.gradle b/plugins/microsoft-graph-authz/kiota-merged/build.gradle similarity index 52% rename from libs/kiota-merged/build.gradle rename to plugins/microsoft-graph-authz/kiota-merged/build.gradle index edd4753ff83c6..b60f8cccfe22c 100644 --- a/libs/kiota-merged/build.gradle +++ b/plugins/microsoft-graph-authz/kiota-merged/build.gradle @@ -1,6 +1,17 @@ +/* + * 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" diff --git a/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE b/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE new file mode 100644 index 0000000000000..9e841e7a26e4e --- /dev/null +++ b/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE @@ -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 diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/plugins/microsoft-graph-authz/src/main/java/module-info.java index db35ba20ec3f7..66b91d780166e 100644 --- a/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ b/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -16,7 +16,6 @@ requires org.elasticsearch.logging; requires org.apache.httpcomponents.httpclient; requires org.apache.httpcomponents.httpcore; - requires com.nimbusds.jose.jwt; requires com.microsoft.kiota; requires com.microsoft.graph; requires com.azure.identity; diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 73aa6b258fd34..491db187466e0 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -16,18 +16,11 @@ import com.microsoft.graph.models.GroupCollectionResponse; import com.microsoft.graph.serviceclient.GraphServiceClient; import com.microsoft.kiota.authentication.AzureIdentityAuthenticationProvider; -import com.nimbusds.jose.util.JSONObjectUtils; import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.HttpClients; -import org.apache.http.message.BasicNameValuePair; import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.SettingsException; import org.elasticsearch.common.util.concurrent.ThreadContext; @@ -42,36 +35,23 @@ import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.core.security.user.User; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.text.ParseException; import java.util.ArrayList; import java.util.List; import java.util.Map; -import static com.nimbusds.jose.util.JSONObjectUtils.getJSONObjectArray; -import static com.nimbusds.jose.util.JSONObjectUtils.getString; - public class MicrosoftGraphAuthzRealm extends Realm { private static final Logger logger = LogManager.getLogger(MicrosoftGraphAuthzRealm.class); private final RealmConfig config; private final UserRoleMapper roleMapper; - private final HttpClient httpClient; private final String clientSecret; public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { - this(roleMapper, config, HttpClients.createDefault()); - } - - /* package-private for testing */ MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config, HttpClient httpClient) { super(config); this.config = config; this.roleMapper = roleMapper; - this.httpClient = httpClient; this.clientSecret = config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); require(MicrosoftGraphAuthzRealmSettings.CLIENT_ID); @@ -85,8 +65,8 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { ); } + // FIXME both of these lines are load bearing, because this project is cursed logger.trace("pls {}", new com.google.gson.JsonParser()); - kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(clientSecret, "clientSecret"); // TODO license check } @@ -116,9 +96,6 @@ public void authenticate(AuthenticationToken token, ActionListener listener) { try { - // final var token = fetchAccessToken(); - // final var userProperties = fetchUserProperties(principal, token); - // final var groups = fetchGroupMembership(principal, token); final var client = buildClient(); final var userProperties = sdkFetchUserProperties(client, principal); final var groups = sdkFetchGroupMembership(client, principal); @@ -142,34 +119,6 @@ public void lookupUser(String principal, ActionListener listener) { } } - private String fetchAccessToken() throws IOException, ParseException { - var request = new HttpPost( - Strings.format( - "%s/%s/oauth2/v2.0/token", - config.getSetting(MicrosoftGraphAuthzRealmSettings.ACCESS_TOKEN_HOST), - config.getSetting(MicrosoftGraphAuthzRealmSettings.TENANT_ID) - ) - ); - request.setEntity( - new UrlEncodedFormEntity( - List.of( - new BasicNameValuePair("grant_type", "client_credentials"), - new BasicNameValuePair("scope", "https://graph.microsoft.com/.default"), - new BasicNameValuePair("client_id", config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_ID)), - new BasicNameValuePair("client_secret", clientSecret.toString()) - ) - ) - ); - logger.trace("getting bearer token from {}", request.getURI()); - final var response = httpClient.execute(request, new BasicResponseHandler()); - - final var json = JSONObjectUtils.parse(response); - final var token = getString(json, "access_token"); - logger.trace("Azure access token [{}]", token); - - return token; - } - private GraphServiceClient buildClient() { logger.trace("building client"); final var credentialProvider = new ClientSecretCredentialBuilder().clientId( @@ -188,27 +137,6 @@ private GraphServiceClient buildClient() { ); } - private Tuple fetchUserProperties(String userId, String token) throws IOException, ParseException { - var request = new HttpGet( - Strings.format( - "%s/v1.0/users/%s?$select=displayName,mail", - config.getSetting(MicrosoftGraphAuthzRealmSettings.API_HOST), - userId - ) - ); - request.addHeader("Authorization", "Bearer " + token); - logger.trace("getting user info from {}", request.getURI()); - final var response = httpClient.execute(request, new BasicResponseHandler()); - - final var json = JSONObjectUtils.parse(response); - final var email = getString(json, "email"); - final var name = getString(json, "displayName"); - - logger.trace("User [{}] has email [{}]", name, email); - - return Tuple.tuple(name, email); - } - private Tuple sdkFetchUserProperties(GraphServiceClient client, String userId) { var response = client.usersWithUserPrincipalName(userId) .get(requestConfig -> requestConfig.queryParameters.select = new String[] { "displayName", "email" }); @@ -218,36 +146,6 @@ private Tuple sdkFetchUserProperties(GraphServiceClient client, return Tuple.tuple(response.getDisplayName(), response.getMail()); } - private List fetchGroupMembership(String userId, String token) throws IOException, ParseException, URISyntaxException { - var request = new HttpGet(); - request.addHeader("Authorization", "Bearer " + token); - - // TODO need to think about transient group membership - // also do we need to care about other memberships, e.g. Directory Roles and "Administrative Units"? - var nextPage = Strings.format( - "%s/v1.0/users/%s/memberOf/microsoft.graph.group?$select=id&$top=999", - config.getSetting(MicrosoftGraphAuthzRealmSettings.API_HOST), - userId - ); - var groups = new ArrayList(); - - while (nextPage != null) { - request.setURI(new URI(nextPage)); - logger.trace("getting group membership from {}", request.getURI()); - final var response = httpClient.execute(request, new BasicResponseHandler()); - - var json = JSONObjectUtils.parse(response); - nextPage = getString(json, "@odata.nextLink"); - for (var groupData : getJSONObjectArray(json, "value")) { - groups.add(getString(groupData, "id")); - } - } - - logger.trace("Got {} groups from Graph {}", groups.size(), String.join(", ", groups)); - - return groups; - } - private List sdkFetchGroupMembership(GraphServiceClient client, String userId) throws ReflectiveOperationException { List groups = new ArrayList<>(); diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index f98147814bc7b..07994e5b9fde4 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -9,7 +9,6 @@ package org.elasticsearch.xpack.security.authz.microsoft; -import org.apache.http.client.HttpClient; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.common.settings.MockSecureSettings; import org.elasticsearch.common.settings.Settings; @@ -64,9 +63,8 @@ public void testLookupUser() { env, threadContext ); - final var client = mock(HttpClient.class); - final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client); + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config); final var future = new PlainActionFuture(); realm.lookupUser("principal", future); final var user = future.actionGet(); From 5984adaee396f69b91dd3cf634112b97d775ca5c Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Fri, 23 May 2025 15:49:06 +0100 Subject: [PATCH 10/66] passing IT tests --- .../azurecore/AzureCoreClassPatcher.java | 14 +-- .../patches/azurecore/ImplUtilsPatcher.java | 4 +- plugins/microsoft-graph-authz/build.gradle | 1 + .../microsoft/MicrosoftGraphAuthzRealm.java | 19 ++-- .../MicrosoftGraphAuthzRealmSettings.java | 5 +- .../microsoft/AzureGraphHttpFixture.java | 96 ++++++++++++------- .../MicrosoftGraphAuthzPluginIT.java | 2 +- 7 files changed, 90 insertions(+), 51 deletions(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java index 9ef828084278f..f9374ab728b1f 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java @@ -55,14 +55,17 @@ public void transform(@NotNull TransformOutputs outputs) { System.out.println("Patching " + inputFile.getName()); // This would be cleaner if gradle artifact transformers supported temp files https://github.com/gradle/gradle/issues/30440. // We need to patch (or delete) the Manifest file as it contains signatures for the class files, which obviously get - // invalidated when we patch the bytecode, but the existing patch tools only work on class files, so we unfortunately need to - // duplicate the loop here. We _also_ need to create a "temporary" jar to write (or not) the patched manifest and the rest of - // the jar contents. + // invalidated when we patch the bytecode, but the existing patch tools only work on class files, so we unfortunately need to + // duplicate the loop here. We _also_ need to create a "temporary" jar to write (or not) the patched manifest and the rest of + // the jar contents. // TODO we could possibly update `Utils.patchJar` to enable patching manifests etc and most of this would be significantly - // simplified + // simplified File firstOutputFile = outputs.file(inputFile.getName().replace(".jar", "-half-patched")); - try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(firstOutputFile))) { + try ( + JarFile jarFile = new JarFile(inputFile); + JarOutputStream jos = new JarOutputStream(new FileOutputStream(firstOutputFile)) + ) { Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); @@ -88,5 +91,4 @@ public void transform(@NotNull TransformOutputs outputs) { } } - } diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java index a552804b3b62f..b283c96f40060 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java @@ -21,8 +21,8 @@ class ImplUtilsPatcher extends 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 strictly forbidden in ES (i.e. it will throw an - // Entitlements error). + // `addShutdownHook` invokes `java.lang.Runtime.addShutdownHook`, which is strictly 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, () -> { diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 34db67bab567f..2a76449beaa11 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -75,6 +75,7 @@ dependencies { 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" attributesSchema { attribute(patched) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 491db187466e0..609dd4e073ac2 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -17,10 +17,9 @@ import com.microsoft.graph.serviceclient.GraphServiceClient; import com.microsoft.kiota.authentication.AzureIdentityAuthenticationProvider; -import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.HttpClients; import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.SettingsException; import org.elasticsearch.common.util.concurrent.ThreadContext; @@ -45,7 +44,7 @@ public class MicrosoftGraphAuthzRealm extends Realm { private final RealmConfig config; private final UserRoleMapper roleMapper; - private final String clientSecret; + private final SecureString clientSecret; public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { super(config); @@ -66,7 +65,7 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { } // FIXME both of these lines are load bearing, because this project is cursed - logger.trace("pls {}", new com.google.gson.JsonParser()); + new com.google.gson.JsonParser(); kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(clientSecret, "clientSecret"); // TODO license check } @@ -96,10 +95,12 @@ public void authenticate(AuthenticationToken token, ActionListener listener) { try { + // TODO probably want to do this once rather than every time final var client = buildClient(); final var userProperties = sdkFetchUserProperties(client, principal); final var groups = sdkFetchGroupMembership(client, principal); + // TODO confirm we don't need any other fields final var userData = new UserRoleMapper.UserData(principal, null, groups, Map.of(), config); roleMapper.resolveRoles(userData, listener.delegateFailureAndWrap((l, roles) -> { @@ -115,6 +116,7 @@ public void lookupUser(String principal, ActionListener listener) { l.onResponse(user); })); } catch (Exception e) { + // TODO logging etc listener.onFailure(e); } } @@ -127,6 +129,8 @@ private GraphServiceClient buildClient() { .clientSecret(clientSecret.toString()) .tenantId(config.getSetting(MicrosoftGraphAuthzRealmSettings.TENANT_ID)) .authorityHost(config.getSetting(MicrosoftGraphAuthzRealmSettings.ACCESS_TOKEN_HOST)) + // TODO this is necessary for tests, but we probably want this enabled in prod + .disableInstanceDiscovery() .build(); return new GraphServiceClient( @@ -138,8 +142,8 @@ private GraphServiceClient buildClient() { } private Tuple sdkFetchUserProperties(GraphServiceClient client, String userId) { - var response = client.usersWithUserPrincipalName(userId) - .get(requestConfig -> requestConfig.queryParameters.select = new String[] { "displayName", "email" }); + var response = client.users().byUserId(userId) + .get(requestConfig -> requestConfig.queryParameters.select = new String[] { "displayName", "mail" }); logger.trace("User [{}] has email [{}]", response.getDisplayName(), response.getMail()); @@ -149,6 +153,9 @@ private Tuple sdkFetchUserProperties(GraphServiceClient client, private List sdkFetchGroupMembership(GraphServiceClient client, String userId) throws ReflectiveOperationException { List groups = new ArrayList<>(); + // TODO figure out exactly what we need to fetch here - we may need to fetch transitive groups as well, and may need to remove + // the `graph.group` cast (i.e. fetch "directory roles" and "administrative units" as well); + // see https://learn.microsoft.com/en-us/graph/api/user-list-transitivememberof var groupMembership = client.users().byUserId(userId).memberOf().graphGroup().get(requestConfig -> { requestConfig.queryParameters.select = new String[] { "id" }; requestConfig.queryParameters.top = 999; diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java index dac664874cdea..485c4f3e8fcff 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java @@ -25,10 +25,9 @@ public class MicrosoftGraphAuthzRealmSettings { Setting.Property.NodeScope ); - public static final Setting.AffixSetting CLIENT_SECRET = RealmSettings.simpleString( + public static final Setting.AffixSetting CLIENT_SECRET = RealmSettings.secureString( REALM_TYPE, - "client_secret", - Setting.Property.NodeScope + "client_secret" ); public static final Setting.AffixSetting TENANT_ID = RealmSettings.simpleString( diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java index 7fdb991d01b96..38982bc0bf2bb 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java @@ -8,7 +8,6 @@ package org.elasticsearch.xpack.security.authz.microsoft; import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsServer; @@ -52,6 +51,7 @@ public class AzureGraphHttpFixture extends ExternalResource { private final String principal; private final String displayName; private final String email; + private final String jwt; private HttpsServer server; @@ -69,14 +69,15 @@ public AzureGraphHttpFixture( this.principal = principal; this.displayName = displayName; this.email = email; + + jwt = "test jwt"; } @Override protected void before() throws Throwable { - final var jwt = "test jwt"; - final var skipToken = UUID.randomUUID().toString(); - - final var certificate = PemUtils.readCertificates(List.of(Path.of(getClass().getClassLoader().getResource("server/cert.pem").toURI()))).getFirst(); + 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( @@ -88,9 +89,31 @@ protected void before() throws Throwable { server = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); server.setHttpsConfigurator(new HttpsConfigurator(sslContext)); + registerGetAccessTokenHandler(); + registerGetUserHandler(); + registerGetUserMembershipHandler(); + + server.createContext("/", exchange -> { + logger.warn("Unhandled request for [{}]", exchange.getRequestURI()); + exchange.sendResponseHeaders(RestStatus.NOT_IMPLEMENTED.getStatus(), 0); + exchange.close(); + }); + server.start(); + } + + @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 -> { if (exchange.getRequestMethod().equals("POST") == false) { - httpError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected POST request"); + graphError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected POST request"); return; } @@ -99,23 +122,27 @@ protected void before() throws Throwable { RestUtils.decodeQueryString(requestBody, 0, formFields); if (formFields.get("grant_type").equals("client_credentials") == false) { - httpError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Grant Type: %s", formFields.get("grant_type"))); + graphError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Grant Type: %s", formFields.get("grant_type"))); return; } if (formFields.get("client_id").equals(clientId) == false) { - httpError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Client ID: %s", formFields.get("client_id"))); + graphError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Client ID: %s", formFields.get("client_id"))); return; } if (formFields.get("client_secret").equals(clientSecret) == false) { - httpError( + graphError( exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Client Secret: %s", formFields.get("client_secret")) ); return; } - if (formFields.get("scope").equals("https://graph.microsoft.com/.default") == false) { - httpError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Scope: %s", formFields.get("scope"))); + 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; } @@ -134,20 +161,23 @@ protected void before() throws Throwable { responseBytes.writeTo(exchange.getResponseBody()); exchange.close(); }); + } + + private void registerGetUserHandler() { server.createContext("/v1.0/users/" + principal, exchange -> { if (exchange.getRequestMethod().equals("GET") == false) { - httpError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected GET request"); + graphError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected GET request"); return; } final var authorization = exchange.getRequestHeaders().getFirst("Authorization"); if (authorization.equals("Bearer " + jwt) == false) { - httpError(exchange, RestStatus.UNAUTHORIZED, Strings.format("Wrong Authorization header: %s", authorization)); + graphError(exchange, RestStatus.UNAUTHORIZED, Strings.format("Wrong Authorization header: %s", authorization)); return; } if (exchange.getRequestURI().getQuery().contains("$select=displayName,mail") == false) { - httpError(exchange, RestStatus.BAD_REQUEST, "Must filter fields using $select"); + graphError(exchange, RestStatus.BAD_REQUEST, "Must filter fields using $select"); return; } @@ -165,21 +195,26 @@ protected void before() throws Throwable { exchange.close(); }); - server.createContext("/v1.0/users/" + principal + "/memberOf/microsoft.graph.group", exchange -> { + } + + private void registerGetUserMembershipHandler() { + final var skipToken = UUID.randomUUID().toString(); + + server.createContext("/v1.0/users/" + principal + "/memberOf", exchange -> { if (exchange.getRequestMethod().equals("GET") == false) { - httpError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected GET request"); + graphError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected GET request"); return; } final var authorization = exchange.getRequestHeaders().getFirst("Authorization"); if (authorization.equals("Bearer " + jwt) == false) { - httpError(exchange, RestStatus.UNAUTHORIZED, Strings.format("Wrong Authorization header: %s", authorization)); + 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 - httpError(exchange, RestStatus.BAD_REQUEST, "Must filter fields using $select"); + graphError(exchange, RestStatus.BAD_REQUEST, "Must filter fields using $select"); return; } @@ -206,25 +241,20 @@ protected void before() throws Throwable { exchange.close(); }); - server.createContext("/", exchange -> { - logger.warn("Unhandled request for [{}]", exchange.getRequestURI()); - exchange.close(); - }); - server.start(); - } - - @Override - protected void after() { - server.stop(0); } - public String getBaseUrl() { - return "https://" + server.getAddress().getHostString() + ":" + server.getAddress().getPort(); - } - - private void httpError(HttpExchange exchange, RestStatus statusCode, String message) throws IOException { + // 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 = message.getBytes(); exchange.sendResponseHeaders(statusCode.getStatus(), responseBytes.length); exchange.getResponseBody().write(responseBytes); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index 438a61d7e8875..4c5fbc1b2a348 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -94,7 +94,7 @@ private static ElasticsearchCluster initTestCluster() { .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) + .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") .systemProperty("javax.net.ssl.trustStore", () -> trustStore.getTrustStorePath().toString()) From 219aa17e2752a95e2426069f40aa7f5063062ffa Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Fri, 23 May 2025 16:09:47 +0100 Subject: [PATCH 11/66] slight clean up --- .../patches/azurecore/AzureCoreClassPatcher.java | 5 +++-- .../dependencies/patches/azurecore/ImplUtilsPatcher.java | 3 +-- .../authz/microsoft/MicrosoftGraphAuthzPluginIT.java | 3 +-- ...zureGraphHttpFixture.java => MsGraphHttpFixture.java} | 9 +++++---- 4 files changed, 10 insertions(+), 10 deletions(-) rename x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/{AzureGraphHttpFixture.java => MsGraphHttpFixture.java} (97%) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java index f9374ab728b1f..eaf60c9931b26 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java @@ -51,11 +51,12 @@ public abstract class AzureCoreClassPatcher implements TransformAction { diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index 4c5fbc1b2a348..c3797e8ec52ad 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -16,7 +16,6 @@ import org.elasticsearch.test.TestTrustStore; import org.elasticsearch.test.XContentTestUtils; import org.elasticsearch.test.cluster.ElasticsearchCluster; -import org.elasticsearch.test.cluster.local.distribution.DistributionType; import org.elasticsearch.test.cluster.local.model.User; import org.elasticsearch.test.cluster.util.resource.Resource; import org.elasticsearch.test.rest.ESRestTestCase; @@ -51,7 +50,7 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { private static final String CLIENT_SECRET = "client_secret"; private static final String USERNAME = "Thor"; - private static final AzureGraphHttpFixture graphFixture = new AzureGraphHttpFixture( + private static final MsGraphHttpFixture graphFixture = new MsGraphHttpFixture( TENANT_ID, CLIENT_ID, CLIENT_SECRET, diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java similarity index 97% rename from x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java rename to x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java index 38982bc0bf2bb..109085ecaff26 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/AzureGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java @@ -41,9 +41,9 @@ import java.util.Map; import java.util.UUID; -public class AzureGraphHttpFixture extends ExternalResource { +public class MsGraphHttpFixture extends ExternalResource { - private static final Logger logger = LogManager.getLogger(AzureGraphHttpFixture.class); + private static final Logger logger = LogManager.getLogger(MsGraphHttpFixture.class); private final String tenantId; private final String clientId; @@ -55,7 +55,7 @@ public class AzureGraphHttpFixture extends ExternalResource { private HttpsServer server; - public AzureGraphHttpFixture( + public MsGraphHttpFixture( String tenantId, String clientId, String clientSecret, @@ -70,7 +70,7 @@ public AzureGraphHttpFixture( this.displayName = displayName; this.email = email; - jwt = "test jwt"; + this.jwt = "test jwt"; } @Override @@ -219,6 +219,7 @@ private void registerGetUserMembershipHandler() { } var nextLink = getBaseUrl() + exchange.getRequestURI().toString() + "&$skiptoken=" + skipToken; + // TODO should really pass this through the constructor or something rather than hard-coding it var groups = new Object[] { Map.of("id", "group-id-1"), Map.of("id", "group-id-2") }; // return multiple pages of results, to ensure client correctly supports paging From c230ed60d2ef8f8191226500c0f7c4a879d0ef02 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 23 May 2025 15:29:12 +0000 Subject: [PATCH 12/66] [CI] Auto commit changes from spotless --- .../microsoft/MicrosoftGraphAuthzRealm.java | 7 ++++--- .../MicrosoftGraphAuthzRealmSettings.java | 5 +---- .../microsoft/MicrosoftGraphAuthzRealmTests.java | 7 +------ .../microsoft/MicrosoftGraphAuthzPluginIT.java | 5 +++-- .../authz/microsoft/MsGraphHttpFixture.java | 16 ++++------------ 5 files changed, 13 insertions(+), 27 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 609dd4e073ac2..f2a58794e6ce1 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -142,7 +142,8 @@ private GraphServiceClient buildClient() { } private Tuple sdkFetchUserProperties(GraphServiceClient client, String userId) { - var response = client.users().byUserId(userId) + var response = client.users() + .byUserId(userId) .get(requestConfig -> requestConfig.queryParameters.select = new String[] { "displayName", "mail" }); logger.trace("User [{}] has email [{}]", response.getDisplayName(), response.getMail()); @@ -154,8 +155,8 @@ private List sdkFetchGroupMembership(GraphServiceClient client, String u List groups = new ArrayList<>(); // TODO figure out exactly what we need to fetch here - we may need to fetch transitive groups as well, and may need to remove - // the `graph.group` cast (i.e. fetch "directory roles" and "administrative units" as well); - // see https://learn.microsoft.com/en-us/graph/api/user-list-transitivememberof + // the `graph.group` cast (i.e. fetch "directory roles" and "administrative units" as well); + // see https://learn.microsoft.com/en-us/graph/api/user-list-transitivememberof var groupMembership = client.users().byUserId(userId).memberOf().graphGroup().get(requestConfig -> { requestConfig.queryParameters.select = new String[] { "id" }; requestConfig.queryParameters.top = 999; diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java index 485c4f3e8fcff..5042c8342b8d6 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java @@ -25,10 +25,7 @@ public class MicrosoftGraphAuthzRealmSettings { Setting.Property.NodeScope ); - public static final Setting.AffixSetting CLIENT_SECRET = RealmSettings.secureString( - REALM_TYPE, - "client_secret" - ); + public static final Setting.AffixSetting CLIENT_SECRET = RealmSettings.secureString(REALM_TYPE, "client_secret"); public static final Setting.AffixSetting TENANT_ID = RealmSettings.simpleString( REALM_TYPE, diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index 07994e5b9fde4..73cd90086603f 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -57,12 +57,7 @@ public void testLookupUser() { .setSecureSettings(secureSettings) .build(); - final var config = new RealmConfig( - realmId, - realmSettings, - env, - threadContext - ); + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config); final var future = new PlainActionFuture(); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index c3797e8ec52ad..7b1aca0abe9d3 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -61,8 +61,9 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { public static ElasticsearchCluster cluster = initTestCluster(); - private static final TestTrustStore trustStore = new TestTrustStore(() -> - MicrosoftGraphAuthzPluginIT.class.getClassLoader().getResourceAsStream("server/cert.pem")); + private static final TestTrustStore trustStore = new TestTrustStore( + () -> MicrosoftGraphAuthzPluginIT.class.getClassLoader().getResourceAsStream("server/cert.pem") + ); @ClassRule public static TestRule ruleChain = RuleChain.outerRule(graphFixture).around(trustStore).around(cluster); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java index 109085ecaff26..225e1c1270e16 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java @@ -8,7 +8,6 @@ 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; @@ -25,9 +24,6 @@ import org.elasticsearch.xcontent.XContentType; import org.junit.rules.ExternalResource; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; - import java.io.IOException; import java.io.InputStreamReader; import java.net.InetAddress; @@ -41,6 +37,9 @@ import java.util.Map; import java.util.UUID; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; + public class MsGraphHttpFixture extends ExternalResource { private static final Logger logger = LogManager.getLogger(MsGraphHttpFixture.class); @@ -55,14 +54,7 @@ public class MsGraphHttpFixture extends ExternalResource { private HttpsServer server; - public MsGraphHttpFixture( - String tenantId, - String clientId, - String clientSecret, - String principal, - String displayName, - String email - ) { + public MsGraphHttpFixture(String tenantId, String clientId, String clientSecret, String principal, String displayName, String email) { this.tenantId = tenantId; this.clientId = clientId; this.clientSecret = clientSecret; From 6d907e56d565d21e41aa80bb63747a24bbff6725 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Fri, 23 May 2025 16:30:06 +0100 Subject: [PATCH 13/66] update comment about mystery includes to be more accurate --- plugins/microsoft-graph-authz/build.gradle | 1 + plugins/microsoft-graph-authz/src/main/java/module-info.java | 3 +++ .../security/authz/microsoft/MicrosoftGraphAuthzRealm.java | 3 --- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 2a76449beaa11..dab71afe42d21 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -76,6 +76,7 @@ dependencies { testRuntimeOnly "net.minidev:json-smart:2.5.2" testRuntimeOnly "com.nimbusds:oauth2-oidc-sdk:11.22.2" + testRuntimeOnly "com.nimbusds:content-type:2.3" attributesSchema { attribute(patched) diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/plugins/microsoft-graph-authz/src/main/java/module-info.java index 66b91d780166e..f57aa369846c9 100644 --- a/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ b/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -20,6 +20,9 @@ requires com.microsoft.graph; requires com.azure.identity; requires com.microsoft.graph.core; + // FIXME both of these module includes are load bearing, because this project is cursed + // we don't need either of these in the plugin, but without the Kotlin include, the plugin fails to compile; without the gson include, + // we get a NoClassDefFoundError at runtime requires kotlin.stdlib; requires com.google.gson; diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index f2a58794e6ce1..f6e1dbbc1a6a3 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -64,9 +64,6 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { ); } - // FIXME both of these lines are load bearing, because this project is cursed - new com.google.gson.JsonParser(); - kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(clientSecret, "clientSecret"); // TODO license check } From 0b9a449e5ebf7fb4120342ab498ec710a59c1eea Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 23 May 2025 15:38:52 +0000 Subject: [PATCH 14/66] [CI] Auto commit changes from spotless --- plugins/microsoft-graph-authz/src/main/java/module-info.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/plugins/microsoft-graph-authz/src/main/java/module-info.java index f57aa369846c9..260a31d1af5fd 100644 --- a/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ b/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -21,8 +21,8 @@ requires com.azure.identity; requires com.microsoft.graph.core; // FIXME both of these module includes are load bearing, because this project is cursed - // we don't need either of these in the plugin, but without the Kotlin include, the plugin fails to compile; without the gson include, - // we get a NoClassDefFoundError at runtime + // we don't need either of these in the plugin, but without the Kotlin include, the plugin fails to compile; without the gson include, + // we get a NoClassDefFoundError at runtime requires kotlin.stdlib; requires com.google.gson; From 451a5539bcc5d0500edee7d3a6f2832a53562701 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Tue, 27 May 2025 11:36:00 +0100 Subject: [PATCH 15/66] clean up some TODOs --- .../src/main/java/module-info.java | 3 -- .../microsoft/MicrosoftGraphAuthzRealm.java | 44 ++++++++++++++----- .../MicrosoftGraphAuthzRealmTests.java | 2 + .../MicrosoftGraphAuthzPluginIT.java | 8 +++- .../authz/microsoft/MsGraphHttpFixture.java | 30 ++++++++++--- 5 files changed, 63 insertions(+), 24 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/plugins/microsoft-graph-authz/src/main/java/module-info.java index 260a31d1af5fd..66b91d780166e 100644 --- a/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ b/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -20,9 +20,6 @@ requires com.microsoft.graph; requires com.azure.identity; requires com.microsoft.graph.core; - // FIXME both of these module includes are load bearing, because this project is cursed - // we don't need either of these in the plugin, but without the Kotlin include, the plugin fails to compile; without the gson include, - // we get a NoClassDefFoundError at runtime requires kotlin.stdlib; requires com.google.gson; diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index f6e1dbbc1a6a3..1e3c55fd5b7ba 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -24,8 +24,12 @@ import org.elasticsearch.common.settings.SettingsException; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.core.Tuple; +import org.elasticsearch.license.License; +import org.elasticsearch.license.LicenseUtils; +import org.elasticsearch.license.LicensedFeature; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; +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; @@ -42,16 +46,27 @@ public class MicrosoftGraphAuthzRealm extends Realm { private static final Logger logger = LogManager.getLogger(MicrosoftGraphAuthzRealm.class); + private static final boolean DISABLE_INSTANCE_DISCOVERY = System.getProperty( + "tests.azure.credentials.disable_instance_discovery", + "false" + ).equals("true"); + + private 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 SecureString clientSecret; + private final GraphServiceClient client; public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { super(config); this.config = config; this.roleMapper = roleMapper; - this.clientSecret = config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); + var clientSecret = config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); require(MicrosoftGraphAuthzRealmSettings.CLIENT_ID); require(MicrosoftGraphAuthzRealmSettings.TENANT_ID); @@ -64,7 +79,7 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { ); } - // TODO license check + client = buildClient(clientSecret); } private void require(Setting.AffixSetting setting) { @@ -91,9 +106,12 @@ public void authenticate(AuthenticationToken token, ActionListener listener) { + if (MICROSOFT_GRAPH_FEATURE.check(XPackPlugin.getSharedLicenseState()) == false) { + listener.onFailure(LicenseUtils.newComplianceException(MICROSOFT_GRAPH_FEATURE.getName())); + return; + } + try { - // TODO probably want to do this once rather than every time - final var client = buildClient(); final var userProperties = sdkFetchUserProperties(client, principal); final var groups = sdkFetchGroupMembership(client, principal); @@ -113,22 +131,24 @@ public void lookupUser(String principal, ActionListener listener) { l.onResponse(user); })); } catch (Exception e) { - // TODO logging etc + logger.error("failed to authenticate with realm", e); listener.onFailure(e); } } - private GraphServiceClient buildClient() { + private GraphServiceClient buildClient(SecureString clientSecret) { logger.trace("building client"); - final var credentialProvider = new ClientSecretCredentialBuilder().clientId( + 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)) - // TODO this is necessary for tests, but we probably want this enabled in prod - .disableInstanceDiscovery() - .build(); + .authorityHost(config.getSetting(MicrosoftGraphAuthzRealmSettings.ACCESS_TOKEN_HOST)); + + if (DISABLE_INSTANCE_DISCOVERY) { + credentialProviderBuilder.disableInstanceDiscovery(); + } + final var credentialProvider = credentialProviderBuilder.build(); return new GraphServiceClient( new BaseGraphRequestAdapter( diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index 73cd90086603f..41b281334d99d 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -83,4 +83,6 @@ public void testHandleGetGroupMembershipError() { public void testGroupMembershipPagination() { fail(); } + + public void testLicenseCheck() { fail(); } } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index 7b1aca0abe9d3..4a7e6d7e0c12f 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -49,6 +49,7 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { 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 MsGraphHttpFixture graphFixture = new MsGraphHttpFixture( TENANT_ID, @@ -56,7 +57,9 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { CLIENT_SECRET, USERNAME, "Thor Odinson", - "thor@oldap.test.elasticsearch.com" + "thor@oldap.test.elasticsearch.com", + new String[] { "unmapped-group-1", "unmapped-group-2", "unmapped-group-3" }, + new String[] { EXPECTED_GROUP } ); public static ElasticsearchCluster cluster = initTestCluster(); @@ -99,6 +102,7 @@ private static ElasticsearchCluster initTestCluster() { .setting("logger.org.elasticsearch.xpack.security.authz.microsoft", "TRACE") .systemProperty("javax.net.ssl.trustStore", () -> trustStore.getTrustStorePath().toString()) .systemProperty("javax.net.ssl.trustStoreType", "jks") + .systemProperty("tests.azure.credentials.disable_instance_discovery", "true") .build(); } @@ -135,7 +139,7 @@ public void setupRoleMapping() throws IOException { .endObject() .startObject() .startObject("field") - .field("groups", "group-id-3") + .field("groups", EXPECTED_GROUP) .endObject() .endObject() .endArray() // "all" diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java index 225e1c1270e16..87df1689f1007 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java @@ -32,6 +32,7 @@ import java.nio.file.Path; import java.security.SecureRandom; import java.security.cert.Certificate; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -50,17 +51,30 @@ public class MsGraphHttpFixture extends ExternalResource { private final String principal; private final String displayName; private final String email; + private final String[] firstGroupsPage; + private final String[] secondGroupsPage; private final String jwt; private HttpsServer server; - public MsGraphHttpFixture(String tenantId, String clientId, String clientSecret, String principal, String displayName, String email) { + public MsGraphHttpFixture( + String tenantId, + String clientId, + String clientSecret, + String principal, + String displayName, + String email, + String[] firstGroupsPage, + String[] secondGroupsPage + ) { this.tenantId = tenantId; this.clientId = clientId; this.clientSecret = clientSecret; this.principal = principal; this.displayName = displayName; this.email = email; + this.firstGroupsPage = firstGroupsPage; + this.secondGroupsPage = secondGroupsPage; this.jwt = "test jwt"; } @@ -211,13 +225,14 @@ private void registerGetUserMembershipHandler() { } var nextLink = getBaseUrl() + exchange.getRequestURI().toString() + "&$skiptoken=" + skipToken; - // TODO should really pass this through the constructor or something rather than hard-coding it - var groups = new Object[] { Map.of("id", "group-id-1"), Map.of("id", "group-id-2") }; + Object[] groups; // return multiple pages of results, to ensure client correctly supports paging if (exchange.getRequestURI().getQuery().contains("$skiptoken")) { - groups = new Object[] { Map.of("id", "group-id-3") }; + groups = Arrays.stream(secondGroupsPage).map(id -> Map.of("id", id)).toArray(); nextLink = null; + } else { + groups = Arrays.stream(firstGroupsPage).map(id -> Map.of("id", id)).toArray(); } final var groupMembership = XContentBuilder.builder(XContentType.JSON.xContent()); @@ -248,9 +263,10 @@ private void graphError(HttpExchange exchange, RestStatus statusCode, String mes errorResponse.endObject(); errorResponse.endObject(); - final var responseBytes = message.getBytes(); - exchange.sendResponseHeaders(statusCode.getStatus(), responseBytes.length); - exchange.getResponseBody().write(responseBytes); + 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(); } From 85d123a415e6f7b257cc666c10bc3f7110530e5d Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 27 May 2025 10:44:24 +0000 Subject: [PATCH 16/66] [CI] Auto commit changes from spotless --- .../authz/microsoft/MicrosoftGraphAuthzRealmTests.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index 41b281334d99d..72095e845f403 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -84,5 +84,7 @@ public void testGroupMembershipPagination() { fail(); } - public void testLicenseCheck() { fail(); } + public void testLicenseCheck() { + fail(); + } } From a0bba425c214f7ca615d77a4d8f7e9e054a89aea Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Tue, 27 May 2025 15:22:37 +0100 Subject: [PATCH 17/66] implement unit tests --- plugins/microsoft-graph-authz/build.gradle | 1 + .../licenses/microsoft-graph-NOTICE.txt | 0 .../microsoft/MicrosoftGraphAuthzRealm.java | 18 +- .../MicrosoftGraphAuthzRealmTests.java | 269 +++++++++++++++--- .../security/licenses/nimbus-LICENSE.txt | 202 +++++++++++++ 5 files changed, 453 insertions(+), 37 deletions(-) create mode 100644 plugins/microsoft-graph-authz/licenses/microsoft-graph-NOTICE.txt create mode 100644 x-pack/plugin/security/licenses/nimbus-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index dab71afe42d21..210dddc5f9426 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -77,6 +77,7 @@ dependencies { 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) diff --git a/plugins/microsoft-graph-authz/licenses/microsoft-graph-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/microsoft-graph-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 1e3c55fd5b7ba..bdd1842efb8cf 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -27,6 +27,7 @@ import org.elasticsearch.license.License; import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.license.LicensedFeature; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; import org.elasticsearch.xpack.core.XPackPlugin; @@ -51,7 +52,7 @@ public class MicrosoftGraphAuthzRealm extends Realm { "false" ).equals("true"); - private static final LicensedFeature.Momentary MICROSOFT_GRAPH_FEATURE = LicensedFeature.momentary( + static final LicensedFeature.Momentary MICROSOFT_GRAPH_FEATURE = LicensedFeature.momentary( "security-realms", "microsoft_graph", License.OperationMode.PLATINUM @@ -60,6 +61,7 @@ public class MicrosoftGraphAuthzRealm extends Realm { private final RealmConfig config; private final UserRoleMapper roleMapper; private final GraphServiceClient client; + private final XPackLicenseState licenseState; public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { super(config); @@ -79,7 +81,17 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { ); } - client = buildClient(clientSecret); + this.client = buildClient(clientSecret); + this.licenseState = XPackPlugin.getSharedLicenseState(); + } + + // for testing + MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config, GraphServiceClient client, XPackLicenseState licenseState) { + super(config); + this.config = config; + this.roleMapper = roleMapper; + this.client = client; + this.licenseState = licenseState; } private void require(Setting.AffixSetting setting) { @@ -106,7 +118,7 @@ public void authenticate(AuthenticationToken token, ActionListener listener) { - if (MICROSOFT_GRAPH_FEATURE.check(XPackPlugin.getSharedLicenseState()) == false) { + if (MICROSOFT_GRAPH_FEATURE.check(licenseState) == false) { listener.onFailure(LicenseUtils.newComplianceException(MICROSOFT_GRAPH_FEATURE.getName())); return; } diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index 72095e845f403..d588d121adc38 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -9,82 +9,283 @@ 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.memberof.MemberOfRequestBuilder; +import com.microsoft.graph.users.item.memberof.graphgroup.GraphGroupRequestBuilder; +import com.microsoft.kiota.RequestAdapter; + +import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.PlainActionFuture; -import org.elasticsearch.common.settings.MockSecureSettings; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; 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.test.ESTestCase; 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.Before; + +import java.util.List; +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 Settings globalSettings; - private Environment env; - private ThreadContext threadContext; + 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); - @Before - public void setup() { - globalSettings = Settings.builder().put("path.home", createTempDir()).build(); - env = TestEnvironment.newEnvironment(globalSettings); - threadContext = new ThreadContext(globalSettings); - } + 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 + ); public void testLookupUser() { - final var realmName = "ms-graph"; - final var realmId = new RealmConfig.RealmIdentifier(MicrosoftGraphAuthzRealmSettings.REALM_TYPE, realmName); + final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName)); - final var roleMapper = mock(UserRoleMapper.class); - final var secureSettings = new MockSecureSettings(); - secureSettings.setString(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET), "client-secret"); final var realmSettings = Settings.builder() .put(globalSettings) .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) - .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_ID), "client-id") - .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.TENANT_ID), "tenant-id") - .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.ACCESS_TOKEN_HOST), "https://localhost:12345") - .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.API_HOST), "https://localhost:12345/v1.0") - .setSecureSettings(secureSettings) .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 userRequestBuilder = mock(UsersRequestBuilder.class); + final var userItemRequestBuilder = mock(UserItemRequestBuilder.class); + final var msUser = new com.microsoft.graph.models.User(); + msUser.setDisplayName(name); + msUser.setMail(email); + + when(client.users()).thenReturn(userRequestBuilder); + when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); + when(userItemRequestBuilder.get(any())).thenReturn(msUser); + + final var memberOfRequestBuilder = mock(MemberOfRequestBuilder.class); + final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); + final var group = new Group(); + group.setId(groupId); + final var groupMembership = new GroupCollectionResponse(); + groupMembership.setValue(List.of(group)); + + when(userItemRequestBuilder.memberOf()).thenReturn(memberOfRequestBuilder); + when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership); + + final var licenseState = MockLicenseState.createMock(); + when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); - final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config); + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState); final var future = new PlainActionFuture(); - realm.lookupUser("principal", future); + realm.lookupUser(username, future); final var user = future.actionGet(); - assertThat(user.principal(), equalTo("principal")); - assertThat(user.email(), equalTo("email")); - assertThat(user.roles(), arrayContaining("role1")); - } - - public void testHandleGetAccessTokenError() { - fail(); + assertThat(user.principal(), equalTo(username)); + assertThat(user.fullName(), equalTo(name)); + assertThat(user.email(), equalTo(email)); + assertThat(user.roles(), arrayContaining(roleName)); } public void testHandleGetUserPropertiesError() { - fail(); + final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName)); + + final var realmSettings = Settings.builder() + .put(globalSettings) + .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) + .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 userRequestBuilder = mock(UsersRequestBuilder.class); + final var userItemRequestBuilder = mock(UserItemRequestBuilder.class); + final var graphError = new ODataError(); + final var error = new MainError(); + error.setCode("badRequest"); + error.setMessage("bad stuff happened"); + graphError.setError(error); + + when(client.users()).thenReturn(userRequestBuilder); + when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); + when(userItemRequestBuilder.get(any())).thenThrow(graphError); + + final var licenseState = MockLicenseState.createMock(); + when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState); + final var future = new PlainActionFuture(); + realm.lookupUser(username, future); + final var thrown = assertThrows(ODataError.class, future::actionGet); + assertThat(thrown.getMessage(), equalTo("bad stuff happened")); } public void testHandleGetGroupMembershipError() { - fail(); + final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName)); + + final var realmSettings = Settings.builder() + .put(globalSettings) + .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) + .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 userRequestBuilder = mock(UsersRequestBuilder.class); + final var userItemRequestBuilder = mock(UserItemRequestBuilder.class); + final var msUser = new com.microsoft.graph.models.User(); + msUser.setDisplayName(name); + msUser.setMail(email); + + when(client.users()).thenReturn(userRequestBuilder); + when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); + when(userItemRequestBuilder.get(any())).thenReturn(msUser); + + final var memberOfRequestBuilder = mock(MemberOfRequestBuilder.class); + final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); + final var graphError = new ODataError(); + final var error = new MainError(); + error.setCode("badRequest"); + error.setMessage("bad stuff happened"); + graphError.setError(error); + + when(userItemRequestBuilder.memberOf()).thenReturn(memberOfRequestBuilder); + when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenThrow(graphError); + + final var licenseState = MockLicenseState.createMock(); + when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState); + final var future = new PlainActionFuture(); + realm.lookupUser(username, future); + final var thrown = assertThrows(ODataError.class, future::actionGet); + assertThat(thrown.getMessage(), equalTo("bad stuff happened")); } public void testGroupMembershipPagination() { - fail(); + 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 = Settings.builder() + .put(globalSettings) + .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) + .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 userRequestBuilder = mock(UsersRequestBuilder.class); + final var userItemRequestBuilder = mock(UserItemRequestBuilder.class); + final var msUser = new com.microsoft.graph.models.User(); + msUser.setDisplayName(name); + msUser.setMail(email); + + when(client.users()).thenReturn(userRequestBuilder); + when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); + when(userItemRequestBuilder.get(any())).thenReturn(msUser); + + final var memberOfRequestBuilder = mock(MemberOfRequestBuilder.class); + final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); + final var group1 = new Group(); + group1.setId(groupId); + final var groupMembership1 = new GroupCollectionResponse(); + groupMembership1.setValue(List.of(group1)); + groupMembership1.setOdataNextLink("http://localhost:12345/page2"); + + final var group2 = new Group(); + group2.setId(groupId2); + final var groupMembership2 = new GroupCollectionResponse(); + groupMembership2.setValue(List.of(group2)); + groupMembership2.setOdataNextLink("http://localhost:12345/page3"); + + final var group3 = new Group(); + group3.setId(groupId3); + final var groupMembership3 = new GroupCollectionResponse(); + groupMembership3.setValue(List.of(group3)); + + when(userItemRequestBuilder.memberOf()).thenReturn(memberOfRequestBuilder); + when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership1); + when(requestAdapter.send(any(), any(), any())).thenReturn(groupMembership2, groupMembership3); + + final var licenseState = MockLicenseState.createMock(); + when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState); + final var future = new PlainActionFuture(); + 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() { - fail(); + final var roleMapper = mock(UserRoleMapper.class); + final var realmSettings = Settings.builder() + .put(globalSettings) + .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) + .build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + + final var licenseState = MockLicenseState.createMock(); + when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(false); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState); + final var future = new PlainActionFuture(); + realm.lookupUser(username, future); + final var thrown = assertThrows(ElasticsearchSecurityException.class, future::actionGet); + assertThat(thrown.getMessage(), equalTo("current license is non-compliant for [microsoft_graph]")); + } + + private UserRoleMapper mockRoleMapper(Set expectedGroups, Set 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>) invocation.getArguments()[1]; + listener.onResponse(rolesToReturn); + return null; + }).when(roleMapper).resolveRoles(any(), any()); + + return roleMapper; } } diff --git a/x-pack/plugin/security/licenses/nimbus-LICENSE.txt b/x-pack/plugin/security/licenses/nimbus-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/x-pack/plugin/security/licenses/nimbus-LICENSE.txt @@ -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. From bec682e50cb9318327c758f6effef2585a068115 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Wed, 28 May 2025 14:54:11 +0100 Subject: [PATCH 18/66] test retry handling --- gradle/verification-metadata.xml | 10 ++++++ plugins/microsoft-graph-authz/build.gradle | 4 +-- x-pack/plugin/security/build.gradle | 4 +-- .../MicrosoftGraphAuthzPluginIT.java | 2 ++ .../authz/microsoft/MsGraphHttpFixture.java | 31 +++++++++++++++++-- 5 files changed, 44 insertions(+), 7 deletions(-) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index cee1bdeabe0a4..8cec4151ff84b 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1168,6 +1168,11 @@ + + + + + @@ -3058,6 +3063,11 @@ + + + + + diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 210dddc5f9426..bf0eafd18049c 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -68,8 +68,8 @@ dependencies { 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.2.0" - runtimeOnly "com.squareup.okio:okio-jvm:3.2.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" runtimeOnly "com.azure:azure-core-http-okhttp:1.12.10" implementation "com.google.code.gson:gson:2.10" diff --git a/x-pack/plugin/security/build.gradle b/x-pack/plugin/security/build.gradle index 940185dd952f4..7be33cf77b728 100644 --- a/x-pack/plugin/security/build.gradle +++ b/x-pack/plugin/security/build.gradle @@ -65,8 +65,7 @@ dependencies { exclude group: 'org.bouncycastle' } 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-slf4j2-impl:2.24.3" api "org.apache.httpcomponents:httpclient:${versions.httpclient}" api "org.apache.httpcomponents:httpcore:${versions.httpcore}" @@ -184,6 +183,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 { diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index 4a7e6d7e0c12f..5ec492cc2491b 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -100,6 +100,8 @@ private static ElasticsearchCluster initTestCluster() { .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") diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java index 87df1689f1007..7195c810c164c 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java @@ -36,11 +36,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; +import static org.elasticsearch.test.ESTestCase.randomBoolean; + public class MsGraphHttpFixture extends ExternalResource { private static final Logger logger = LogManager.getLogger(MsGraphHttpFixture.class); @@ -53,7 +57,11 @@ public class MsGraphHttpFixture extends ExternalResource { private final String email; private final String[] firstGroupsPage; private final String[] secondGroupsPage; - private final String jwt; + 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; @@ -75,8 +83,6 @@ public MsGraphHttpFixture( this.email = email; this.firstGroupsPage = firstGroupsPage; this.secondGroupsPage = secondGroupsPage; - - this.jwt = "test jwt"; } @Override @@ -105,6 +111,7 @@ protected void before() throws Throwable { exchange.close(); }); server.start(); + logger.info("Started server on port [{}]", server.getAddress().getPort()); } @Override @@ -118,6 +125,9 @@ public String getBaseUrl() { private void registerGetAccessTokenHandler() { server.createContext("/" + tenantId + "/oauth2/v2.0/token", exchange -> { + logger.info("Received access token request"); + loginCount.incrementAndGet(); + if (exchange.getRequestMethod().equals("POST") == false) { graphError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected POST request"); return; @@ -171,6 +181,9 @@ private void registerGetAccessTokenHandler() { private void registerGetUserHandler() { server.createContext("/v1.0/users/" + principal, 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; @@ -187,6 +200,15 @@ private void registerGetUserHandler() { 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", displayName); @@ -207,6 +229,9 @@ private void registerGetUserMembershipHandler() { final var skipToken = UUID.randomUUID().toString(); server.createContext("/v1.0/users/" + principal + "/memberOf", exchange -> { + logger.info("Received get user membership request [{}]", exchange.getRequestURI()); + getGroupMembershipCount.incrementAndGet(); + if (exchange.getRequestMethod().equals("GET") == false) { graphError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected GET request"); return; From 3798b98746fcfeb45f5d0db88db4d7f1d64ba598 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 28 May 2025 14:01:33 +0000 Subject: [PATCH 19/66] [CI] Auto commit changes from spotless --- .../xpack/security/authz/microsoft/MsGraphHttpFixture.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java index 7195c810c164c..bf7508608392d 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java @@ -36,15 +36,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Random; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; -import static org.elasticsearch.test.ESTestCase.randomBoolean; - public class MsGraphHttpFixture extends ExternalResource { private static final Logger logger = LogManager.getLogger(MsGraphHttpFixture.class); From 4552b3e9b8ff53f61ed691fdcbf539cedab987b4 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Wed, 28 May 2025 16:46:57 +0100 Subject: [PATCH 20/66] fetch transitive group membership --- plugins/microsoft-graph-authz/build.gradle | 4 ++++ .../microsoft/MicrosoftGraphAuthzRealm.java | 11 +++++----- .../MicrosoftGraphAuthzRealmTests.java | 12 +++++------ .../MicrosoftGraphAuthzPluginIT.java | 20 ++++++++++++------- .../authz/microsoft/MsGraphHttpFixture.java | 2 +- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index bf0eafd18049c..6a745a295f3eb 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -92,3 +92,7 @@ dependencies { } tasks.named("javadoc").configure { enabled = false } + +tasks.named("dependencyLicenses").configure { + mapping from: "microsoft-graph-core", to: "microsoft-graph" +} diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index bdd1842efb8cf..6127fbd916df1 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -12,6 +12,8 @@ import com.azure.identity.ClientSecretCredentialBuilder; import com.microsoft.graph.core.requests.BaseGraphRequestAdapter; import com.microsoft.graph.core.tasks.PageIterator; +import com.microsoft.graph.models.DirectoryObject; +import com.microsoft.graph.models.DirectoryObjectCollectionResponse; import com.microsoft.graph.models.Group; import com.microsoft.graph.models.GroupCollectionResponse; import com.microsoft.graph.serviceclient.GraphServiceClient; @@ -183,17 +185,14 @@ private Tuple sdkFetchUserProperties(GraphServiceClient client, private List sdkFetchGroupMembership(GraphServiceClient client, String userId) throws ReflectiveOperationException { List groups = new ArrayList<>(); - // TODO figure out exactly what we need to fetch here - we may need to fetch transitive groups as well, and may need to remove - // the `graph.group` cast (i.e. fetch "directory roles" and "administrative units" as well); - // see https://learn.microsoft.com/en-us/graph/api/user-list-transitivememberof - var groupMembership = client.users().byUserId(userId).memberOf().graphGroup().get(requestConfig -> { + var groupMembership = client.users().byUserId(userId).transitiveMemberOf().get(requestConfig -> { requestConfig.queryParameters.select = new String[] { "id" }; requestConfig.queryParameters.top = 999; }); - var pageIterator = new PageIterator.Builder().client(client) + var pageIterator = new PageIterator.Builder().client(client) .collectionPage(groupMembership) - .collectionPageFactory(GroupCollectionResponse::createFromDiscriminatorValue) + .collectionPageFactory(DirectoryObjectCollectionResponse::createFromDiscriminatorValue) .requestConfigurator(requestInfo -> { requestInfo.addQueryParameter("%24select", new String[] { "id" }); requestInfo.addQueryParameter("%24top", "999"); diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index d588d121adc38..fc1f04550c37b 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -9,6 +9,7 @@ package org.elasticsearch.xpack.security.authz.microsoft; +import com.microsoft.graph.models.DirectoryObjectCollectionResponse; import com.microsoft.graph.models.Group; import com.microsoft.graph.models.GroupCollectionResponse; import com.microsoft.graph.models.odataerrors.MainError; @@ -18,6 +19,7 @@ import com.microsoft.graph.users.item.UserItemRequestBuilder; import com.microsoft.graph.users.item.memberof.MemberOfRequestBuilder; import com.microsoft.graph.users.item.memberof.graphgroup.GraphGroupRequestBuilder; +import com.microsoft.graph.users.item.transitivememberof.TransitiveMemberOfRequestBuilder; import com.microsoft.kiota.RequestAdapter; import org.elasticsearch.ElasticsearchSecurityException; @@ -88,16 +90,14 @@ public void testLookupUser() { when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); when(userItemRequestBuilder.get(any())).thenReturn(msUser); - final var memberOfRequestBuilder = mock(MemberOfRequestBuilder.class); - final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); + final var memberOfRequestBuilder = mock(TransitiveMemberOfRequestBuilder.class); final var group = new Group(); group.setId(groupId); - final var groupMembership = new GroupCollectionResponse(); + final var groupMembership = new DirectoryObjectCollectionResponse(); groupMembership.setValue(List.of(group)); - when(userItemRequestBuilder.memberOf()).thenReturn(memberOfRequestBuilder); - when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); - when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership); + when(userItemRequestBuilder.transitiveMemberOf()).thenReturn(memberOfRequestBuilder); + when(memberOfRequestBuilder.get(any())).thenReturn(groupMembership); final var licenseState = MockLicenseState.createMock(); when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index 5ec492cc2491b..754337f4440b7 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -7,7 +7,11 @@ package org.elasticsearch.xpack.security.authz.microsoft; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionTestUtils; +import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; @@ -174,7 +178,13 @@ protected boolean shouldConfigureProjects() { } public void testAuthenticationSuccessful() throws Exception { - samlAuthWithMicrosoftGraphAuthz(USERNAME, getSamlAssertionJsonBodyString(USERNAME)); + final var listener = new PlainActionFuture(); + samlAuthWithMicrosoftGraphAuthz(USERNAME, getSamlAssertionJsonBodyString(USERNAME), listener); + final var resp = entityAsMap(listener.get()); + List 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")); } private String getSamlAssertionJsonBodyString(String username) throws Exception { @@ -191,14 +201,10 @@ private String getSamlAssertionJsonBodyString(String username) throws Exception return Strings.toString(JsonXContent.contentBuilder().map(body)); } - private void samlAuthWithMicrosoftGraphAuthz(String username, String samlAssertion) throws Exception { + private void samlAuthWithMicrosoftGraphAuthz(String username, String samlAssertion, ActionListener listener) { var req = new Request("POST", "_security/saml/authenticate"); req.setJsonEntity(samlAssertion); - var resp = entityAsMap(client().performRequest(req)); - List 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")); + client().performRequestAsync(req, ActionTestUtils.wrapAsRestResponseListener(listener)); } } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java index bf7508608392d..b726230df5e11 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java @@ -225,7 +225,7 @@ private void registerGetUserHandler() { private void registerGetUserMembershipHandler() { final var skipToken = UUID.randomUUID().toString(); - server.createContext("/v1.0/users/" + principal + "/memberOf", exchange -> { + server.createContext("/v1.0/users/" + principal + "/transitiveMemberOf", exchange -> { logger.info("Received get user membership request [{}]", exchange.getRequestURI()); getGroupMembershipCount.incrementAndGet(); From d1c22bfcb6569f735f6a11345a4d560f78b7b334 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 28 May 2025 15:54:54 +0000 Subject: [PATCH 21/66] [CI] Auto commit changes from spotless --- .../security/authz/microsoft/MicrosoftGraphAuthzRealm.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 6127fbd916df1..0ec6577888311 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -14,8 +14,6 @@ import com.microsoft.graph.core.tasks.PageIterator; import com.microsoft.graph.models.DirectoryObject; import com.microsoft.graph.models.DirectoryObjectCollectionResponse; -import com.microsoft.graph.models.Group; -import com.microsoft.graph.models.GroupCollectionResponse; import com.microsoft.graph.serviceclient.GraphServiceClient; import com.microsoft.kiota.authentication.AzureIdentityAuthenticationProvider; From ab0c69d9f0d0915a8f7745207bbff43284089e0a Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 29 May 2025 10:30:37 +0100 Subject: [PATCH 22/66] support multiple test users in graph http fixture --- .../MicrosoftGraphAuthzPluginIT.java | 15 ++++-- .../authz/microsoft/MsGraphHttpFixture.java | 48 +++++++------------ .../security/authz/microsoft/TestUser.java | 11 +++++ 3 files changed, 39 insertions(+), 35 deletions(-) create mode 100644 x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index 754337f4440b7..2999aa904cdb7 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -59,11 +59,16 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { TENANT_ID, CLIENT_ID, CLIENT_SECRET, - USERNAME, - "Thor Odinson", - "thor@oldap.test.elasticsearch.com", - new String[] { "unmapped-group-1", "unmapped-group-2", "unmapped-group-3" }, - new String[] { EXPECTED_GROUP } + List.of( + new TestUser( + USERNAME, + "Thor Odinson", + "thor@oldap.test.elasticsearch.com", + new String[] { "unmapped-group-1", "unmapped-group-2", "unmapped-group-3", EXPECTED_GROUP }, + new String[] { "microsoft_graph_user" } + ) + ), + 3 ); public static ElasticsearchCluster cluster = initTestCluster(); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java index b726230df5e11..e9410c0fc43f0 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java @@ -49,11 +49,8 @@ public class MsGraphHttpFixture extends ExternalResource { private final String tenantId; private final String clientId; private final String clientSecret; - private final String principal; - private final String displayName; - private final String email; - private final String[] firstGroupsPage; - private final String[] secondGroupsPage; + private final List users; + private final int groupsPageSize; private final String jwt = "test jwt"; private final AtomicInteger loginCount = new AtomicInteger(0); @@ -62,24 +59,12 @@ public class MsGraphHttpFixture extends ExternalResource { private HttpsServer server; - public MsGraphHttpFixture( - String tenantId, - String clientId, - String clientSecret, - String principal, - String displayName, - String email, - String[] firstGroupsPage, - String[] secondGroupsPage - ) { + public MsGraphHttpFixture(String tenantId, String clientId, String clientSecret, List users, int groupsPageSize) { this.tenantId = tenantId; this.clientId = clientId; this.clientSecret = clientSecret; - this.principal = principal; - this.displayName = displayName; - this.email = email; - this.firstGroupsPage = firstGroupsPage; - this.secondGroupsPage = secondGroupsPage; + this.users = users; + this.groupsPageSize = groupsPageSize; } @Override @@ -99,8 +84,11 @@ protected void before() throws Throwable { server.setHttpsConfigurator(new HttpsConfigurator(sslContext)); registerGetAccessTokenHandler(); - registerGetUserHandler(); - registerGetUserMembershipHandler(); + + for (TestUser user : users) { + registerGetUserHandler(user); + registerGetUserMembershipHandler(user); + } server.createContext("/", exchange -> { logger.warn("Unhandled request for [{}]", exchange.getRequestURI()); @@ -176,8 +164,8 @@ private void registerGetAccessTokenHandler() { }); } - private void registerGetUserHandler() { - server.createContext("/v1.0/users/" + principal, exchange -> { + 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(); @@ -208,8 +196,8 @@ private void registerGetUserHandler() { var userProperties = XContentBuilder.builder(XContentType.JSON.xContent()); userProperties.startObject(); - userProperties.field("displayName", displayName); - userProperties.field("mail", email); + userProperties.field("displayName", user.displayName()); + userProperties.field("mail", user.email()); userProperties.endObject(); var responseBytes = BytesReference.bytes(userProperties); @@ -222,10 +210,10 @@ private void registerGetUserHandler() { }); } - private void registerGetUserMembershipHandler() { + private void registerGetUserMembershipHandler(TestUser user) { final var skipToken = UUID.randomUUID().toString(); - server.createContext("/v1.0/users/" + principal + "/transitiveMemberOf", exchange -> { + server.createContext("/v1.0/users/" + user.username() + "/transitiveMemberOf", exchange -> { logger.info("Received get user membership request [{}]", exchange.getRequestURI()); getGroupMembershipCount.incrementAndGet(); @@ -251,10 +239,10 @@ private void registerGetUserMembershipHandler() { // return multiple pages of results, to ensure client correctly supports paging if (exchange.getRequestURI().getQuery().contains("$skiptoken")) { - groups = Arrays.stream(secondGroupsPage).map(id -> Map.of("id", id)).toArray(); + groups = Arrays.stream(user.groups()).skip(groupsPageSize).map(id -> Map.of("id", id)).toArray(); nextLink = null; } else { - groups = Arrays.stream(firstGroupsPage).map(id -> Map.of("id", id)).toArray(); + groups = Arrays.stream(user.groups()).limit(groupsPageSize).map(id -> Map.of("id", id)).toArray(); } final var groupMembership = XContentBuilder.builder(XContentType.JSON.xContent()); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java new file mode 100644 index 0000000000000..1040ed655beb4 --- /dev/null +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java @@ -0,0 +1,11 @@ +/* + * 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; + +public record TestUser(String username, String displayName, String email, String[] groups, String[] roles) { +} From 227e0d9d5963331e65f398b2b47e300cea856dd0 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Thu, 29 May 2025 09:39:48 +0000 Subject: [PATCH 23/66] [CI] Auto commit changes from spotless --- .../elasticsearch/xpack/security/authz/microsoft/TestUser.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java index 1040ed655beb4..b212ddf17e47b 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java @@ -7,5 +7,4 @@ package org.elasticsearch.xpack.security.authz.microsoft; -public record TestUser(String username, String displayName, String email, String[] groups, String[] roles) { -} +public record TestUser(String username, String displayName, String email, String[] groups, String[] roles) {} From 668c441fc552bd2bcfcb4e7900f899c1cdf3e462 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 29 May 2025 10:50:57 +0100 Subject: [PATCH 24/66] move manifest patching into Utils.patchJar --- .../internal/dependencies/patches/Utils.java | 16 +- .../azurecore/AzureCoreClassPatcher.java | 40 +--- .../licenses/log4j-slf4j-impl-LICENSE.txt | 202 ++++++++++++++++++ .../licenses/log4j-slf4j-impl-NOTICE.txt | 20 ++ 4 files changed, 236 insertions(+), 42 deletions(-) create mode 100644 x-pack/plugin/security/licenses/log4j-slf4j-impl-LICENSE.txt create mode 100644 x-pack/plugin/security/licenses/log4j-slf4j-impl-NOTICE.txt diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java index 63831b6f062ce..92f71e91e9ea0 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java @@ -60,6 +60,10 @@ public String toString() { } } + public static void patchJar(File inputJar, File outputJar, Collection patchers) { + patchJar(inputJar, outputJar, patchers, true); + } + /** * Patches the classes in the input JAR file, using the collection of patchers. Each patcher specifies a target class (its jar entry * name) and the SHA256 digest on the class bytes. @@ -69,8 +73,10 @@ public String toString() { * @param inputFile the JAR file to patch * @param outputFile the output (patched) JAR file * @param patchers list of patcher info (classes to patch (jar entry name + optional SHA256 digest) and ASM visitor to transform them) + * @param preserveManifest whether to include the manifest file from the input JAR; set this to false when patching a signed JAR, + * otherwise the patched classes will fail to load at runtime due to mismatched signatures */ - public static void patchJar(File inputFile, File outputFile, Collection patchers) { + public static void patchJar(File inputFile, File outputFile, Collection patchers, boolean preserveManifest) { var classPatchers = patchers.stream().collect(Collectors.toMap(PatcherInfo::jarEntryName, Function.identity())); var mismatchedClasses = new ArrayList(); try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(outputFile))) { @@ -101,9 +107,11 @@ public static void patchJar(File inputFile, File outputFile, Collection entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String entryName = entry.getName(); - - if (entryName.endsWith("MANIFEST.MF") == false) { - jos.putNextEntry(new JarEntry(entryName)); - try (InputStream is = jarFile.getInputStream(entry)) { - is.transferTo(jos); - } - jos.closeEntry(); - } - } - } catch (Exception e) { - throw new RuntimeException(e); - } - - File secondOutputFile = outputs.file(inputFile.getName().replace(".jar", "-patched.jar")); - Utils.patchJar(firstOutputFile, secondOutputFile, CLASS_PATCHERS); + File outputFile = outputs.file(inputFile.getName().replace(".jar", "-patched.jar")); + Utils.patchJar(inputFile, outputFile, CLASS_PATCHERS, false); } else { System.out.println("Skipping " + inputFile.getName()); outputs.file(getInputArtifact()); diff --git a/x-pack/plugin/security/licenses/log4j-slf4j-impl-LICENSE.txt b/x-pack/plugin/security/licenses/log4j-slf4j-impl-LICENSE.txt new file mode 100644 index 0000000000000..6279e5206de13 --- /dev/null +++ b/x-pack/plugin/security/licenses/log4j-slf4j-impl-LICENSE.txt @@ -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 1999-2005 The Apache Software Foundation + + 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. diff --git a/x-pack/plugin/security/licenses/log4j-slf4j-impl-NOTICE.txt b/x-pack/plugin/security/licenses/log4j-slf4j-impl-NOTICE.txt new file mode 100644 index 0000000000000..bbb5fb3f66e2a --- /dev/null +++ b/x-pack/plugin/security/licenses/log4j-slf4j-impl-NOTICE.txt @@ -0,0 +1,20 @@ +Apache Log4j +Copyright 1999-2023 Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +ResolverUtil.java +Copyright 2005-2006 Tim Fennell + +Dumbster SMTP test server +Copyright 2004 Jason Paul Kitchen + +TypeUtil.java +Copyright 2002-2012 Ramnivas Laddad, Juergen Hoeller, Chris Beams + +picocli (http://picocli.info) +Copyright 2017 Remko Popma + +TimeoutBlockingWaitStrategy.java and parts of Util.java +Copyright 2011 LMAX Ltd. From be296bebbc60ae0289409026dcfee68d2066d29e Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 29 May 2025 12:19:28 +0100 Subject: [PATCH 25/66] add test for concurrent login --- plugins/microsoft-graph-authz/build.gradle | 1 + .../licenses/azure-NOTICE.txt | 0 .../MicrosoftGraphAuthzPluginIT.java | 55 +++++++++++++------ .../authz/microsoft/MsGraphHttpFixture.java | 10 ++-- .../security/authz/microsoft/TestUser.java | 4 +- 5 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 plugins/microsoft-graph-authz/licenses/azure-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 6a745a295f3eb..ed11530a1ed31 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -95,4 +95,5 @@ tasks.named("javadoc").configure { enabled = false } tasks.named("dependencyLicenses").configure { mapping from: "microsoft-graph-core", to: "microsoft-graph" + mapping from: "azure-identity", to: "azure" } diff --git a/plugins/microsoft-graph-authz/licenses/azure-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/azure-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index 2999aa904cdb7..a41ffa06eda85 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -9,6 +9,7 @@ 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.Response; @@ -40,6 +41,7 @@ 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; @@ -55,19 +57,23 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { private static final String USERNAME = "Thor"; private static final String EXPECTED_GROUP = "test_group"; + private static final List TEST_USERS = List.of( + new 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 TestUser("User2", "User 2", "user2@example.com", List.of(EXPECTED_GROUP), List.of("microsoft_graph_user")), + new TestUser("User3", "User 3", "user3@example.com", List.of(), List.of()) + ); + private static final MsGraphHttpFixture graphFixture = new MsGraphHttpFixture( TENANT_ID, CLIENT_ID, CLIENT_SECRET, - List.of( - new TestUser( - USERNAME, - "Thor Odinson", - "thor@oldap.test.elasticsearch.com", - new String[] { "unmapped-group-1", "unmapped-group-2", "unmapped-group-3", EXPECTED_GROUP }, - new String[] { "microsoft_graph_user" } - ) - ), + TEST_USERS, 3 ); @@ -140,11 +146,6 @@ public void setupRoleMapping() throws IOException { .startArray("all") .startObject() .startObject("field") - .field("username", USERNAME) - .endObject() - .endObject() - .startObject() - .startObject("field") .field("realm.name", "microsoft_graph1") .endObject() .endObject() @@ -183,15 +184,33 @@ protected boolean shouldConfigureProjects() { } public void testAuthenticationSuccessful() throws Exception { - final var listener = new PlainActionFuture(); + final var listener = new PlainActionFuture>(); samlAuthWithMicrosoftGraphAuthz(USERNAME, getSamlAssertionJsonBodyString(USERNAME), listener); - final var resp = entityAsMap(listener.get()); + final var resp = listener.get(); List 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 resultsListener = new PlainActionFuture>>(); + final var groupedListener = new GroupedActionListener<>(TEST_USERS.size(), resultsListener); + for (var user : TEST_USERS) { + samlAuthWithMicrosoftGraphAuthz(user.username(), getSamlAssertionJsonBodyString(user.username()), groupedListener); + } + final var responses = resultsListener.get(); + + assertThat(responses.size(), equalTo(TEST_USERS.size())); + for (var user : TEST_USERS) { + var response = responses.stream().filter(r -> r.get("username").equals(user.username())).findFirst(); + assertTrue(response.isPresent()); + final List roles = new XContentTestUtils.JsonMapView(response.get()).get("authentication.roles"); + assertThat(roles, equalTo(user.roles())); + assertThat(ObjectPath.evaluate(response.get(), "authentication.authentication_realm.name"), equalTo("saml1")); + } + } + private String getSamlAssertionJsonBodyString(String username) throws Exception { var message = new SamlResponseBuilder().spEntityId("http://sp/default.example.org/") .idpEntityId(IDP_ENTITY_ID) @@ -206,10 +225,10 @@ private String getSamlAssertionJsonBodyString(String username) throws Exception return Strings.toString(JsonXContent.contentBuilder().map(body)); } - private void samlAuthWithMicrosoftGraphAuthz(String username, String samlAssertion, ActionListener listener) { + private void samlAuthWithMicrosoftGraphAuthz(String username, String samlAssertion, ActionListener> listener) { var req = new Request("POST", "_security/saml/authenticate"); req.setJsonEntity(samlAssertion); - client().performRequestAsync(req, ActionTestUtils.wrapAsRestResponseListener(listener)); + client().performRequestAsync(req, ActionTestUtils.wrapAsRestResponseListener(listener.map(ESRestTestCase::entityAsMap))); } } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java index e9410c0fc43f0..d7e4ea8aa68c8 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java @@ -234,15 +234,17 @@ private void registerGetUserMembershipHandler(TestUser user) { return; } - var nextLink = getBaseUrl() + exchange.getRequestURI().toString() + "&$skiptoken=" + skipToken; + String nextLink = null; Object[] groups; // return multiple pages of results, to ensure client correctly supports paging if (exchange.getRequestURI().getQuery().contains("$skiptoken")) { - groups = Arrays.stream(user.groups()).skip(groupsPageSize).map(id -> Map.of("id", id)).toArray(); - nextLink = null; + groups = user.groups().stream().skip(groupsPageSize).map(id -> Map.of("id", id)).toArray(); } else { - groups = Arrays.stream(user.groups()).limit(groupsPageSize).map(id -> Map.of("id", id)).toArray(); + 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()); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java index b212ddf17e47b..2da3d96233287 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java @@ -7,4 +7,6 @@ package org.elasticsearch.xpack.security.authz.microsoft; -public record TestUser(String username, String displayName, String email, String[] groups, String[] roles) {} +import java.util.List; + +public record TestUser(String username, String displayName, String email, List groups, List roles) {} From 681d0955b1b157512ac4086100adb71474abbc01 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Thu, 29 May 2025 11:31:33 +0000 Subject: [PATCH 26/66] [CI] Auto commit changes from spotless --- .../authz/microsoft/MicrosoftGraphAuthzPluginIT.java | 9 +-------- .../security/authz/microsoft/MsGraphHttpFixture.java | 1 - 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index a41ffa06eda85..ac6c737c9fa63 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -12,7 +12,6 @@ import org.elasticsearch.action.support.GroupedActionListener; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.client.Request; -import org.elasticsearch.client.Response; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; @@ -69,13 +68,7 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { new TestUser("User3", "User 3", "user3@example.com", List.of(), List.of()) ); - private static final MsGraphHttpFixture graphFixture = new MsGraphHttpFixture( - TENANT_ID, - CLIENT_ID, - CLIENT_SECRET, - TEST_USERS, - 3 - ); + private static final MsGraphHttpFixture graphFixture = new MsGraphHttpFixture(TENANT_ID, CLIENT_ID, CLIENT_SECRET, TEST_USERS, 3); public static ElasticsearchCluster cluster = initTestCluster(); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java index d7e4ea8aa68c8..b74aff06f0962 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java @@ -32,7 +32,6 @@ import java.nio.file.Path; import java.security.SecureRandom; import java.security.cert.Certificate; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; From 128f95468a1b88c81f8bd35df76dc644469dc6fe Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 29 May 2025 13:56:13 +0100 Subject: [PATCH 27/66] remove hard-coded version from azure-core patcher --- .../patches/azurecore/AzureCoreClassPatcher.java | 6 +++--- plugins/microsoft-graph-authz/build.gradle | 2 +- ...slf4j-impl-LICENSE.txt => log4j-slf4j2-impl-LICENSE.txt} | 0 ...j-slf4j-impl-NOTICE.txt => log4j-slf4j2-impl-NOTICE.txt} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename x-pack/plugin/security/licenses/{log4j-slf4j-impl-LICENSE.txt => log4j-slf4j2-impl-LICENSE.txt} (100%) rename x-pack/plugin/security/licenses/{log4j-slf4j-impl-NOTICE.txt => log4j-slf4j2-impl-NOTICE.txt} (100%) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java index c6dd72aaadff3..dddd44ecf722c 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java @@ -22,12 +22,13 @@ import java.io.File; import java.util.List; +import java.util.regex.Pattern; import static org.elasticsearch.gradle.internal.dependencies.patches.PatcherInfo.classPatcher; public abstract class AzureCoreClassPatcher implements TransformAction { - private static final String JAR_FILE_TO_PATCH = "azure-core-1.55.3.jar"; + private static final String JAR_FILE_TO_PATCH = "azure-core-[\\d.]*\\.jar"; private static final List CLASS_PATCHERS = List.of( classPatcher( @@ -45,8 +46,7 @@ public abstract class AzureCoreClassPatcher implements TransformAction Date: Thu, 29 May 2025 14:25:45 +0100 Subject: [PATCH 28/66] add missing NOTICE file --- x-pack/plugin/security/licenses/nimbus-NOTICE.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 x-pack/plugin/security/licenses/nimbus-NOTICE.txt diff --git a/x-pack/plugin/security/licenses/nimbus-NOTICE.txt b/x-pack/plugin/security/licenses/nimbus-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d From de8a31e516d584f5627708caa832091e0ceabe7c Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 29 May 2025 14:40:24 +0100 Subject: [PATCH 29/66] remove unused licenses --- .../security/licenses/slf4j-nop-LICENSE.txt | 24 ------------------- .../security/licenses/slf4j-nop-NOTICE.txt | 0 2 files changed, 24 deletions(-) delete mode 100644 x-pack/plugin/security/licenses/slf4j-nop-LICENSE.txt delete mode 100644 x-pack/plugin/security/licenses/slf4j-nop-NOTICE.txt diff --git a/x-pack/plugin/security/licenses/slf4j-nop-LICENSE.txt b/x-pack/plugin/security/licenses/slf4j-nop-LICENSE.txt deleted file mode 100644 index 508a27283f65f..0000000000000 --- a/x-pack/plugin/security/licenses/slf4j-nop-LICENSE.txt +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2004-2007 QOS.ch -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - - diff --git a/x-pack/plugin/security/licenses/slf4j-nop-NOTICE.txt b/x-pack/plugin/security/licenses/slf4j-nop-NOTICE.txt deleted file mode 100644 index e69de29bb2d1d..0000000000000 From 48e180c55e4005f9bd16c4d454e8a6577b8d6b28 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 29 May 2025 15:41:21 +0100 Subject: [PATCH 30/66] fix license mapping --- .../gradle/internal/dependencies/patches/Utils.java | 2 +- plugins/microsoft-graph-authz/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java index 92f71e91e9ea0..ccbe5affedddc 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java @@ -107,7 +107,7 @@ public static void patchJar(File inputFile, File outputFile, Collection Date: Fri, 30 May 2025 11:48:49 +0100 Subject: [PATCH 31/66] rewrite azure-core jar unsigner --- .../internal/dependencies/patches/Utils.java | 17 +- .../azurecore/AzureCoreClassPatcher.java | 2 +- x-pack/plugin/security/build.gradle | 3 +- .../licenses/log4j-slf4j2-impl-LICENSE.txt | 202 ------------------ .../licenses/log4j-slf4j2-impl-NOTICE.txt | 20 -- 5 files changed, 15 insertions(+), 229 deletions(-) delete mode 100644 x-pack/plugin/security/licenses/log4j-slf4j2-impl-LICENSE.txt delete mode 100644 x-pack/plugin/security/licenses/log4j-slf4j2-impl-NOTICE.txt diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java index ccbe5affedddc..ac86b1da413f7 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java @@ -27,6 +27,8 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; @@ -61,7 +63,7 @@ public String toString() { } public static void patchJar(File inputJar, File outputJar, Collection patchers) { - patchJar(inputJar, outputJar, patchers, true); + patchJar(inputJar, outputJar, patchers, false); } /** @@ -73,10 +75,11 @@ public static void patchJar(File inputJar, File outputJar, CollectionUnderstanding Signing and Verification */ - public static void patchJar(File inputFile, File outputFile, Collection patchers, boolean preserveManifest) { + public static void patchJar(File inputFile, File outputFile, Collection patchers, boolean unsignJar) { var classPatchers = patchers.stream().collect(Collectors.toMap(PatcherInfo::jarEntryName, Function.identity())); var mismatchedClasses = new ArrayList(); try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(outputFile))) { @@ -107,7 +110,11 @@ public static void patchJar(File inputFile, File outputFile, Collection Date: Fri, 30 May 2025 10:58:00 +0000 Subject: [PATCH 32/66] [CI] Auto commit changes from spotless --- .../gradle/internal/dependencies/patches/Utils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java index ac86b1da413f7..888774ad26549 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java @@ -28,7 +28,6 @@ import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; -import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; From 09ca5ca779bc5eb46475ba9fb1477b3cc4885eeb Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Fri, 30 May 2025 12:09:40 +0100 Subject: [PATCH 33/66] restore missing license --- .../security/licenses/slf4j-nop-LICENSE.txt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 x-pack/plugin/security/licenses/slf4j-nop-LICENSE.txt diff --git a/x-pack/plugin/security/licenses/slf4j-nop-LICENSE.txt b/x-pack/plugin/security/licenses/slf4j-nop-LICENSE.txt new file mode 100644 index 0000000000000..508a27283f65f --- /dev/null +++ b/x-pack/plugin/security/licenses/slf4j-nop-LICENSE.txt @@ -0,0 +1,24 @@ +Copyright (c) 2004-2007 QOS.ch +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + From b44eddbcc289bd00fb8810e66a5783cb53732760 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Fri, 30 May 2025 12:19:31 +0100 Subject: [PATCH 34/66] restore missing notice --- x-pack/plugin/security/licenses/slf4j-nop-NOTICE.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 x-pack/plugin/security/licenses/slf4j-nop-NOTICE.txt diff --git a/x-pack/plugin/security/licenses/slf4j-nop-NOTICE.txt b/x-pack/plugin/security/licenses/slf4j-nop-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d From a7d4102d7e36e1cc9b3d1e673ac1fe3984612437 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Fri, 30 May 2025 13:06:01 +0100 Subject: [PATCH 35/66] fix license mapping --- .../internal/dependencies/patches/Utils.java | 14 +++++++------- plugins/microsoft-graph-authz/build.gradle | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java index 888774ad26549..6224b8ab0430d 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java @@ -109,13 +109,13 @@ public static void patchJar(File inputFile, File outputFile, Collection Date: Fri, 30 May 2025 13:20:52 +0100 Subject: [PATCH 36/66] fix license mapping --- plugins/microsoft-graph-authz/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 4f6d46539c14c..1957b76e74b90 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -96,5 +96,5 @@ 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: /jackson/, to: "jackson" } From bdd9aa1e327639435fe84de221ac94caf67aa2b8 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Fri, 30 May 2025 14:23:44 +0100 Subject: [PATCH 37/66] execute blocking graph calls on generic thread pool --- plugins/microsoft-graph-authz/build.gradle | 2 +- .../microsoft/MicrosoftGraphAuthzPlugin.java | 5 +- .../microsoft/MicrosoftGraphAuthzRealm.java | 63 +++++++++++-------- .../MicrosoftGraphAuthzRealmTests.java | 50 +++++++-------- 4 files changed, 68 insertions(+), 52 deletions(-) diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 1957b76e74b90..0744167c585c7 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -96,5 +96,5 @@ 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: /jackson.*/, to: "jackson" } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java index 6986090a83929..09663e5407561 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java @@ -20,7 +20,10 @@ public class MicrosoftGraphAuthzPlugin extends Plugin implements SecurityExtension { @Override public Map getRealms(SecurityComponents components) { - return Map.of(MicrosoftGraphAuthzRealmSettings.REALM_TYPE, config -> new MicrosoftGraphAuthzRealm(components.roleMapper(), config)); + return Map.of( + MicrosoftGraphAuthzRealmSettings.REALM_TYPE, + config -> new MicrosoftGraphAuthzRealm(components.roleMapper(), config, components.threadPool()) + ); } @Override diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 0ec6577888311..3d765d92402ed 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -30,6 +30,7 @@ import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; +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; @@ -42,6 +43,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutorService; public class MicrosoftGraphAuthzRealm extends Realm { @@ -62,8 +64,9 @@ public class MicrosoftGraphAuthzRealm extends Realm { private final UserRoleMapper roleMapper; private final GraphServiceClient client; private final XPackLicenseState licenseState; + private final ExecutorService genericExecutor; - public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { + public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config, ThreadPool threadPool) { super(config); this.config = config; @@ -83,15 +86,23 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config) { this.client = buildClient(clientSecret); this.licenseState = XPackPlugin.getSharedLicenseState(); + this.genericExecutor = threadPool.generic(); } // for testing - MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config, GraphServiceClient client, XPackLicenseState licenseState) { + MicrosoftGraphAuthzRealm( + UserRoleMapper roleMapper, + RealmConfig config, + GraphServiceClient client, + XPackLicenseState licenseState, + ThreadPool threadPool + ) { super(config); this.config = config; this.roleMapper = roleMapper; this.client = client; this.licenseState = licenseState; + this.genericExecutor = threadPool.generic(); } private void require(Setting.AffixSetting setting) { @@ -123,29 +134,31 @@ public void lookupUser(String principal, ActionListener listener) { return; } - try { - final var userProperties = sdkFetchUserProperties(client, principal); - final var groups = sdkFetchGroupMembership(client, principal); - - // TODO confirm we don't need any other fields - 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("Entra ID user {}", user); - l.onResponse(user); - })); - } catch (Exception e) { - logger.error("failed to authenticate with realm", e); - listener.onFailure(e); - } + genericExecutor.execute(() -> { + try { + final var userProperties = sdkFetchUserProperties(client, principal); + final var groups = sdkFetchGroupMembership(client, principal); + + // TODO confirm we don't need any other fields + 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("Entra ID user {}", user); + l.onResponse(user); + })); + } catch (Exception e) { + logger.error("failed to authenticate with realm", e); + listener.onFailure(e); + } + }); } private GraphServiceClient buildClient(SecureString clientSecret) { diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index fc1f04550c37b..736bef42566b9 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -11,17 +11,13 @@ import com.microsoft.graph.models.DirectoryObjectCollectionResponse; 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.memberof.MemberOfRequestBuilder; -import com.microsoft.graph.users.item.memberof.graphgroup.GraphGroupRequestBuilder; import com.microsoft.graph.users.item.transitivememberof.TransitiveMemberOfRequestBuilder; import com.microsoft.kiota.RequestAdapter; - import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.PlainActionFuture; @@ -32,10 +28,13 @@ import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.license.MockLicenseState; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.TestThreadPool; +import org.elasticsearch.threadpool.ThreadPool; 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 java.util.List; import java.util.Set; @@ -46,15 +45,14 @@ 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; +import static org.mockito.Mockito.*; 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); @@ -67,6 +65,12 @@ public class MicrosoftGraphAuthzRealmTests extends ESTestCase { realmName ); + @After + public void tearDown() throws Exception { + super.tearDown(); + terminate(threadPool); + } + public void testLookupUser() { final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName)); @@ -102,7 +106,7 @@ public void testLookupUser() { final var licenseState = MockLicenseState.createMock(); when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); - final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState); + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); final var future = new PlainActionFuture(); realm.lookupUser(username, future); final var user = future.actionGet(); @@ -140,7 +144,7 @@ public void testHandleGetUserPropertiesError() { final var licenseState = MockLicenseState.createMock(); when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); - final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState); + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); final var future = new PlainActionFuture(); realm.lookupUser(username, future); final var thrown = assertThrows(ODataError.class, future::actionGet); @@ -170,22 +174,20 @@ public void testHandleGetGroupMembershipError() { when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); when(userItemRequestBuilder.get(any())).thenReturn(msUser); - final var memberOfRequestBuilder = mock(MemberOfRequestBuilder.class); - final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); + final var memberOfRequestBuilder = mock(TransitiveMemberOfRequestBuilder.class); final var graphError = new ODataError(); final var error = new MainError(); error.setCode("badRequest"); error.setMessage("bad stuff happened"); graphError.setError(error); - when(userItemRequestBuilder.memberOf()).thenReturn(memberOfRequestBuilder); - when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); - when(graphGroupRequestBuilder.get(any())).thenThrow(graphError); + when(userItemRequestBuilder.transitiveMemberOf()).thenReturn(memberOfRequestBuilder); + when(memberOfRequestBuilder.get(any())).thenThrow(graphError); final var licenseState = MockLicenseState.createMock(); when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); - final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState); + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); final var future = new PlainActionFuture(); realm.lookupUser(username, future); final var thrown = assertThrows(ODataError.class, future::actionGet); @@ -218,34 +220,32 @@ public void testGroupMembershipPagination() { when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); when(userItemRequestBuilder.get(any())).thenReturn(msUser); - final var memberOfRequestBuilder = mock(MemberOfRequestBuilder.class); - final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); + final var memberOfRequestBuilder = mock(TransitiveMemberOfRequestBuilder.class); final var group1 = new Group(); group1.setId(groupId); - final var groupMembership1 = new GroupCollectionResponse(); + final var groupMembership1 = new DirectoryObjectCollectionResponse(); groupMembership1.setValue(List.of(group1)); groupMembership1.setOdataNextLink("http://localhost:12345/page2"); final var group2 = new Group(); group2.setId(groupId2); - final var groupMembership2 = new GroupCollectionResponse(); + final var groupMembership2 = new DirectoryObjectCollectionResponse(); groupMembership2.setValue(List.of(group2)); groupMembership2.setOdataNextLink("http://localhost:12345/page3"); final var group3 = new Group(); group3.setId(groupId3); - final var groupMembership3 = new GroupCollectionResponse(); + final var groupMembership3 = new DirectoryObjectCollectionResponse(); groupMembership3.setValue(List.of(group3)); - when(userItemRequestBuilder.memberOf()).thenReturn(memberOfRequestBuilder); - when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); - when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership1); + when(userItemRequestBuilder.transitiveMemberOf()).thenReturn(memberOfRequestBuilder); + when(memberOfRequestBuilder.get(any())).thenReturn(groupMembership1); when(requestAdapter.send(any(), any(), any())).thenReturn(groupMembership2, groupMembership3); final var licenseState = MockLicenseState.createMock(); when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); - final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState); + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); final var future = new PlainActionFuture(); realm.lookupUser(username, future); final var user = future.actionGet(); @@ -268,7 +268,7 @@ public void testLicenseCheck() { final var licenseState = MockLicenseState.createMock(); when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(false); - final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState); + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); final var future = new PlainActionFuture(); realm.lookupUser(username, future); final var thrown = assertThrows(ElasticsearchSecurityException.class, future::actionGet); From 0ed032c70a80cd0d8462fd555947fb10659b0bac Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 30 May 2025 13:32:57 +0000 Subject: [PATCH 38/66] [CI] Auto commit changes from spotless --- .../security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index 736bef42566b9..306ebcc627acc 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -18,6 +18,7 @@ import com.microsoft.graph.users.item.UserItemRequestBuilder; import com.microsoft.graph.users.item.transitivememberof.TransitiveMemberOfRequestBuilder; import com.microsoft.kiota.RequestAdapter; + import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.PlainActionFuture; From 3100eeb74b5f6c4b25768eb35da279625a1ad6da Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Fri, 30 May 2025 15:10:32 +0100 Subject: [PATCH 39/66] fetch only security group membership --- .../microsoft/MicrosoftGraphAuthzRealm.java | 8 +++--- .../MicrosoftGraphAuthzRealmTests.java | 27 ++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 3d765d92402ed..1a0540596d0aa 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -14,6 +14,8 @@ import com.microsoft.graph.core.tasks.PageIterator; import com.microsoft.graph.models.DirectoryObject; import com.microsoft.graph.models.DirectoryObjectCollectionResponse; +import com.microsoft.graph.models.Group; +import com.microsoft.graph.models.GroupCollectionResponse; import com.microsoft.graph.serviceclient.GraphServiceClient; import com.microsoft.kiota.authentication.AzureIdentityAuthenticationProvider; @@ -196,14 +198,14 @@ private Tuple sdkFetchUserProperties(GraphServiceClient client, private List sdkFetchGroupMembership(GraphServiceClient client, String userId) throws ReflectiveOperationException { List groups = new ArrayList<>(); - var groupMembership = client.users().byUserId(userId).transitiveMemberOf().get(requestConfig -> { + var groupMembership = client.users().byUserId(userId).transitiveMemberOf().graphGroup().get(requestConfig -> { requestConfig.queryParameters.select = new String[] { "id" }; requestConfig.queryParameters.top = 999; }); - var pageIterator = new PageIterator.Builder().client(client) + var pageIterator = new PageIterator.Builder().client(client) .collectionPage(groupMembership) - .collectionPageFactory(DirectoryObjectCollectionResponse::createFromDiscriminatorValue) + .collectionPageFactory(GroupCollectionResponse::createFromDiscriminatorValue) .requestConfigurator(requestInfo -> { requestInfo.addQueryParameter("%24select", new String[] { "id" }); requestInfo.addQueryParameter("%24top", "999"); diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index 306ebcc627acc..579485c89830b 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -9,13 +9,14 @@ package org.elasticsearch.xpack.security.authz.microsoft; -import com.microsoft.graph.models.DirectoryObjectCollectionResponse; 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.graphgroup.GraphGroupRequestBuilder; import com.microsoft.graph.users.item.transitivememberof.TransitiveMemberOfRequestBuilder; import com.microsoft.kiota.RequestAdapter; @@ -46,7 +47,9 @@ import static org.hamcrest.Matchers.equalTo; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.doAnswer; public class MicrosoftGraphAuthzRealmTests extends ESTestCase { @@ -96,13 +99,15 @@ public void testLookupUser() { when(userItemRequestBuilder.get(any())).thenReturn(msUser); final var memberOfRequestBuilder = mock(TransitiveMemberOfRequestBuilder.class); + final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); final var group = new Group(); group.setId(groupId); - final var groupMembership = new DirectoryObjectCollectionResponse(); + final var groupMembership = new GroupCollectionResponse(); groupMembership.setValue(List.of(group)); when(userItemRequestBuilder.transitiveMemberOf()).thenReturn(memberOfRequestBuilder); - when(memberOfRequestBuilder.get(any())).thenReturn(groupMembership); + when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership); final var licenseState = MockLicenseState.createMock(); when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); @@ -176,6 +181,7 @@ public void testHandleGetGroupMembershipError() { when(userItemRequestBuilder.get(any())).thenReturn(msUser); final var memberOfRequestBuilder = mock(TransitiveMemberOfRequestBuilder.class); + final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); final var graphError = new ODataError(); final var error = new MainError(); error.setCode("badRequest"); @@ -183,7 +189,8 @@ public void testHandleGetGroupMembershipError() { graphError.setError(error); when(userItemRequestBuilder.transitiveMemberOf()).thenReturn(memberOfRequestBuilder); - when(memberOfRequestBuilder.get(any())).thenThrow(graphError); + when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenThrow(graphError); final var licenseState = MockLicenseState.createMock(); when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); @@ -222,25 +229,27 @@ public void testGroupMembershipPagination() { when(userItemRequestBuilder.get(any())).thenReturn(msUser); final var memberOfRequestBuilder = mock(TransitiveMemberOfRequestBuilder.class); + final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); final var group1 = new Group(); group1.setId(groupId); - final var groupMembership1 = new DirectoryObjectCollectionResponse(); + final var groupMembership1 = new GroupCollectionResponse(); groupMembership1.setValue(List.of(group1)); groupMembership1.setOdataNextLink("http://localhost:12345/page2"); final var group2 = new Group(); group2.setId(groupId2); - final var groupMembership2 = new DirectoryObjectCollectionResponse(); + final var groupMembership2 = new GroupCollectionResponse(); groupMembership2.setValue(List.of(group2)); groupMembership2.setOdataNextLink("http://localhost:12345/page3"); final var group3 = new Group(); group3.setId(groupId3); - final var groupMembership3 = new DirectoryObjectCollectionResponse(); + final var groupMembership3 = new GroupCollectionResponse(); groupMembership3.setValue(List.of(group3)); when(userItemRequestBuilder.transitiveMemberOf()).thenReturn(memberOfRequestBuilder); - when(memberOfRequestBuilder.get(any())).thenReturn(groupMembership1); + when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership1); when(requestAdapter.send(any(), any(), any())).thenReturn(groupMembership2, groupMembership3); final var licenseState = MockLicenseState.createMock(); From af348fd5f53f4ef3396727336ee1559a8b8dc095 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Fri, 30 May 2025 15:21:11 +0100 Subject: [PATCH 40/66] Update docs/changelog/128396.yaml --- docs/changelog/128396.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/128396.yaml diff --git a/docs/changelog/128396.yaml b/docs/changelog/128396.yaml new file mode 100644 index 0000000000000..6e19a83d156e5 --- /dev/null +++ b/docs/changelog/128396.yaml @@ -0,0 +1,5 @@ +pr: 128396 +summary: Delegated authorization using Microsoft Graph (SDK) +area: Authorization +type: feature +issues: [] From 55fb5dfe2d06814ad3e76dea816a570a9fa65706 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 30 May 2025 14:32:36 +0000 Subject: [PATCH 41/66] [CI] Auto commit changes from spotless --- .../security/authz/microsoft/MicrosoftGraphAuthzRealm.java | 2 -- .../authz/microsoft/MicrosoftGraphAuthzRealmTests.java | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 1a0540596d0aa..0ab0216c22f90 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -12,8 +12,6 @@ import com.azure.identity.ClientSecretCredentialBuilder; import com.microsoft.graph.core.requests.BaseGraphRequestAdapter; import com.microsoft.graph.core.tasks.PageIterator; -import com.microsoft.graph.models.DirectoryObject; -import com.microsoft.graph.models.DirectoryObjectCollectionResponse; import com.microsoft.graph.models.Group; import com.microsoft.graph.models.GroupCollectionResponse; import com.microsoft.graph.serviceclient.GraphServiceClient; diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index 579485c89830b..80180ac91b040 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -16,8 +16,8 @@ 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.graphgroup.GraphGroupRequestBuilder; import com.microsoft.graph.users.item.transitivememberof.TransitiveMemberOfRequestBuilder; +import com.microsoft.graph.users.item.transitivememberof.graphgroup.GraphGroupRequestBuilder; import com.microsoft.kiota.RequestAdapter; import org.elasticsearch.ElasticsearchSecurityException; @@ -47,9 +47,9 @@ import static org.hamcrest.Matchers.equalTo; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class MicrosoftGraphAuthzRealmTests extends ESTestCase { From f5ac89940e99a621a082b0dbf7ca105d0c95ebed Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Mon, 2 Jun 2025 10:23:05 +0100 Subject: [PATCH 42/66] fix security thirdPartyAudit task --- plugins/microsoft-graph-authz/build.gradle | 1 + x-pack/plugin/security/build.gradle | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 0744167c585c7..33aa88b7f8572 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -97,4 +97,5 @@ 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" } diff --git a/x-pack/plugin/security/build.gradle b/x-pack/plugin/security/build.gradle index f960fc9891687..987e489fbf09f 100644 --- a/x-pack/plugin/security/build.gradle +++ b/x-pack/plugin/security/build.gradle @@ -416,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', From 9b17cb6d5696fac3f5124daeec243302d3c43081 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Mon, 2 Jun 2025 11:17:02 +0100 Subject: [PATCH 43/66] fix license mapping --- plugins/microsoft-graph-authz/build.gradle | 4 + .../licenses/gson-NOTICE.txt | 0 .../licenses/nimbus-LICENSE.txt | 202 ------------------ .../licenses/okhttp-NOTICE.txt | 0 .../licenses/okio-NOTICE.txt | 0 .../licenses/std-uritemplate-NOTICE.txt | 0 6 files changed, 4 insertions(+), 202 deletions(-) create mode 100644 plugins/microsoft-graph-authz/licenses/gson-NOTICE.txt delete mode 100644 plugins/microsoft-graph-authz/licenses/nimbus-LICENSE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/okhttp-NOTICE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/okio-NOTICE.txt create mode 100644 plugins/microsoft-graph-authz/licenses/std-uritemplate-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 33aa88b7f8572..605cd3f221a45 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -98,4 +98,8 @@ tasks.named("dependencyLicenses").configure { 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" } diff --git a/plugins/microsoft-graph-authz/licenses/gson-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/gson-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/plugins/microsoft-graph-authz/licenses/nimbus-LICENSE.txt b/plugins/microsoft-graph-authz/licenses/nimbus-LICENSE.txt deleted file mode 100644 index d645695673349..0000000000000 --- a/plugins/microsoft-graph-authz/licenses/nimbus-LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - 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. diff --git a/plugins/microsoft-graph-authz/licenses/okhttp-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/okhttp-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/plugins/microsoft-graph-authz/licenses/okio-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/okio-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/plugins/microsoft-graph-authz/licenses/std-uritemplate-NOTICE.txt b/plugins/microsoft-graph-authz/licenses/std-uritemplate-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d From 1f6b7134a030ff6aa1d74a529f7ba0bcb808a969 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Mon, 2 Jun 2025 13:13:14 +0100 Subject: [PATCH 44/66] ignore IT in FIPS mode --- .../security/qa/microsoft-graph-authz-tests/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle index fa365e3ad36b8..e11b901fb40a4 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle @@ -7,3 +7,7 @@ dependencies { clusterPlugins project(':plugins:microsoft-graph-authz') clusterModules project(":modules:analysis-common") } + +tasks.named("javaRestTest").configureEach { + buildParams.withFipsEnabledOnly(it) +} From 2c7cc90ff34e590604dec1ab46e7ad8f422a9382 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Mon, 2 Jun 2025 13:22:02 +0100 Subject: [PATCH 45/66] fix build file --- .../plugin/security/qa/microsoft-graph-authz-tests/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle index e11b901fb40a4..067d2cb5d41d3 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle @@ -8,6 +8,6 @@ dependencies { clusterModules project(":modules:analysis-common") } -tasks.named("javaRestTest").configureEach { +tasks.named("javaRestTest").configure { buildParams.withFipsEnabledOnly(it) } From fc93217ab7f87a6c44b58ff96bb6a42616bfaab1 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Mon, 2 Jun 2025 15:03:38 +0100 Subject: [PATCH 46/66] fix thirdPartyAudit --- plugins/microsoft-graph-authz/build.gradle | 89 +++++++++++++++++++ .../kiota-merged/build.gradle | 46 ++++++++++ .../kiota-merged/licenses/kiota-NOTICE | 0 3 files changed, 135 insertions(+) create mode 100644 plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 605cd3f221a45..152b3f3822557 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -103,3 +103,92 @@ tasks.named("dependencyLicenses").configure { 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' + ) +} diff --git a/plugins/microsoft-graph-authz/kiota-merged/build.gradle b/plugins/microsoft-graph-authz/kiota-merged/build.gradle index b60f8cccfe22c..e714522ab13b8 100644 --- a/plugins/microsoft-graph-authz/kiota-merged/build.gradle +++ b/plugins/microsoft-graph-authz/kiota-merged/build.gradle @@ -27,3 +27,49 @@ tasks.named('shadowJar').configure { 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' + ) +} diff --git a/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE b/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE new file mode 100644 index 0000000000000..e69de29bb2d1d From 2fc5fcd9083b617942b9238771640933e24e20bf Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Mon, 2 Jun 2025 16:43:55 +0100 Subject: [PATCH 47/66] address review comments --- .../patches/{hdfs => }/MethodReplacement.java | 2 +- .../azurecore/AzureCoreClassPatcher.java | 2 + .../patches/azurecore/ImplUtilsPatcher.java | 2 +- .../patches/hdfs/ShellPatcher.java | 1 + .../hdfs/ShutdownHookManagerPatcher.java | 1 + gradle/verification-metadata.xml | 5 --- .../microsoft/MicrosoftGraphAuthzRealm.java | 44 +++++++++---------- .../microsoft-graph-authz-tests/build.gradle | 1 + .../MicrosoftGraphAuthzPluginIT.java | 2 +- ...re.java => MicrosoftGraphHttpFixture.java} | 6 +-- 10 files changed, 31 insertions(+), 35 deletions(-) rename build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/{hdfs => }/MethodReplacement.java (94%) rename x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/{MsGraphHttpFixture.java => MicrosoftGraphHttpFixture.java} (97%) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/MethodReplacement.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/MethodReplacement.java similarity index 94% rename from build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/MethodReplacement.java rename to build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/MethodReplacement.java index 0d6af88b0111e..20bc03dde2ae3 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/MethodReplacement.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/MethodReplacement.java @@ -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; diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java index 4a9661077d49c..43b7381fbbfc1 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java @@ -11,6 +11,7 @@ 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; @@ -26,6 +27,7 @@ import static org.elasticsearch.gradle.internal.dependencies.patches.PatcherInfo.classPatcher; +@CacheableTransform public abstract class AzureCoreClassPatcher implements TransformAction { private static final String JAR_FILE_TO_PATCH = "azure-core-[\\d.]*\\.jar"; diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java index 7361977746e9d..a76f8cb9468ff 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java @@ -9,7 +9,7 @@ package org.elasticsearch.gradle.internal.dependencies.patches.azurecore; -import org.elasticsearch.gradle.internal.dependencies.patches.hdfs.MethodReplacement; +import org.elasticsearch.gradle.internal.dependencies.patches.MethodReplacement; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShellPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShellPatcher.java index ab63249f5c8e8..25b802aa95228 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShellPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShellPatcher.java @@ -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; diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShutdownHookManagerPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShutdownHookManagerPatcher.java index 4efe48a3bf72d..dbac740b208a7 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShutdownHookManagerPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShutdownHookManagerPatcher.java @@ -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; diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 8cec4151ff84b..3ce9c2cb5172c 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -3063,11 +3063,6 @@ - - - - - diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 0ab0216c22f90..e63d4b0d698c5 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -14,6 +14,7 @@ import com.microsoft.graph.core.tasks.PageIterator; import com.microsoft.graph.models.Group; import com.microsoft.graph.models.GroupCollectionResponse; +import com.microsoft.graph.models.odataerrors.ODataError; import com.microsoft.graph.serviceclient.GraphServiceClient; import com.microsoft.kiota.authentication.AzureIdentityAuthenticationProvider; @@ -43,7 +44,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutorService; +import java.util.stream.Collectors; public class MicrosoftGraphAuthzRealm extends Realm { @@ -64,7 +65,7 @@ public class MicrosoftGraphAuthzRealm extends Realm { private final UserRoleMapper roleMapper; private final GraphServiceClient client; private final XPackLicenseState licenseState; - private final ExecutorService genericExecutor; + private final ThreadPool threadPool; public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config, ThreadPool threadPool) { super(config); @@ -74,19 +75,12 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config, T var clientSecret = config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); require(MicrosoftGraphAuthzRealmSettings.CLIENT_ID); + require(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); require(MicrosoftGraphAuthzRealmSettings.TENANT_ID); - if (clientSecret.isEmpty()) { - throw new SettingsException( - "The configuration setting [" - + RealmSettings.getFullSettingKey(config, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET) - + "] is required" - ); - } - this.client = buildClient(clientSecret); this.licenseState = XPackPlugin.getSharedLicenseState(); - this.genericExecutor = threadPool.generic(); + this.threadPool = threadPool; } // for testing @@ -102,12 +96,12 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config, T this.roleMapper = roleMapper; this.client = client; this.licenseState = licenseState; - this.genericExecutor = threadPool.generic(); + this.threadPool = threadPool; } - private void require(Setting.AffixSetting setting) { + private void require(Setting.AffixSetting setting) { final var value = config.getSetting(setting); - if (value.isEmpty()) { + if (value.toString().isEmpty()) { throw new SettingsException("The configuration setting [" + RealmSettings.getFullSettingKey(config, setting) + "] is required"); } } @@ -134,12 +128,11 @@ public void lookupUser(String principal, ActionListener listener) { return; } - genericExecutor.execute(() -> { + threadPool.generic().execute(() -> { try { - final var userProperties = sdkFetchUserProperties(client, principal); - final var groups = sdkFetchGroupMembership(client, principal); + final var userProperties = fetchUserProperties(client, principal); + final var groups = fetchGroupMembership(client, principal); - // TODO confirm we don't need any other fields final var userData = new UserRoleMapper.UserData(principal, null, groups, Map.of(), config); roleMapper.resolveRoles(userData, listener.delegateFailureAndWrap((l, roles) -> { @@ -151,10 +144,10 @@ public void lookupUser(String principal, ActionListener listener) { Map.of(), true ); - logger.trace("Entra ID user {}", user); + logger.trace("Authorized user from Microsoft Graph {}", user); l.onResponse(user); })); - } catch (Exception e) { + } catch (ReflectiveOperationException | ODataError e) { logger.error("failed to authenticate with realm", e); listener.onFailure(e); } @@ -162,7 +155,6 @@ public void lookupUser(String principal, ActionListener listener) { } private GraphServiceClient buildClient(SecureString clientSecret) { - logger.trace("building client"); final var credentialProviderBuilder = new ClientSecretCredentialBuilder().clientId( config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_ID) ) @@ -183,17 +175,17 @@ private GraphServiceClient buildClient(SecureString clientSecret) { ); } - private Tuple sdkFetchUserProperties(GraphServiceClient client, String userId) { + private Tuple fetchUserProperties(GraphServiceClient client, String userId) { var response = client.users() .byUserId(userId) .get(requestConfig -> requestConfig.queryParameters.select = new String[] { "displayName", "mail" }); - logger.trace("User [{}] has email [{}]", response.getDisplayName(), response.getMail()); + logger.trace("Fetched user with name [{}] and email [{}] from Microsoft Graph", response.getDisplayName(), response.getMail()); return Tuple.tuple(response.getDisplayName(), response.getMail()); } - private List sdkFetchGroupMembership(GraphServiceClient client, String userId) throws ReflectiveOperationException { + private List fetchGroupMembership(GraphServiceClient client, String userId) throws ReflectiveOperationException { List groups = new ArrayList<>(); var groupMembership = client.users().byUserId(userId).transitiveMemberOf().graphGroup().get(requestConfig -> { @@ -217,6 +209,10 @@ private List sdkFetchGroupMembership(GraphServiceClient client, String u pageIterator.iterate(); + if (logger.isTraceEnabled()) { + logger.trace("Fetched [{}] groups from Microsoft Graph: [{}]", groups.size(), String.join(", ", groups)); + } + return groups; } } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle index 067d2cb5d41d3..2b8a89e69251d 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle @@ -9,5 +9,6 @@ dependencies { } 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) } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index ac6c737c9fa63..2e0d3fe9513a4 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -68,7 +68,7 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { new TestUser("User3", "User 3", "user3@example.com", List.of(), List.of()) ); - private static final MsGraphHttpFixture graphFixture = new MsGraphHttpFixture(TENANT_ID, CLIENT_ID, CLIENT_SECRET, TEST_USERS, 3); + private static final MicrosoftGraphHttpFixture graphFixture = new MicrosoftGraphHttpFixture(TENANT_ID, CLIENT_ID, CLIENT_SECRET, TEST_USERS, 3); public static ElasticsearchCluster cluster = initTestCluster(); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java similarity index 97% rename from x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java rename to x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java index b74aff06f0962..1f98c79df7a1f 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MsGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java @@ -41,9 +41,9 @@ import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; -public class MsGraphHttpFixture extends ExternalResource { +public class MicrosoftGraphHttpFixture extends ExternalResource { - private static final Logger logger = LogManager.getLogger(MsGraphHttpFixture.class); + private static final Logger logger = LogManager.getLogger(MicrosoftGraphHttpFixture.class); private final String tenantId; private final String clientId; @@ -58,7 +58,7 @@ public class MsGraphHttpFixture extends ExternalResource { private HttpsServer server; - public MsGraphHttpFixture(String tenantId, String clientId, String clientSecret, List users, int groupsPageSize) { + public MicrosoftGraphHttpFixture(String tenantId, String clientId, String clientSecret, List users, int groupsPageSize) { this.tenantId = tenantId; this.clientId = clientId; this.clientSecret = clientSecret; From 64a7eb5ffc6971def3a5ac4f75f7f92668390b48 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Mon, 2 Jun 2025 15:54:10 +0000 Subject: [PATCH 48/66] [CI] Auto commit changes from spotless --- .../authz/microsoft/MicrosoftGraphAuthzRealm.java | 1 - .../authz/microsoft/MicrosoftGraphAuthzPluginIT.java | 8 +++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index e63d4b0d698c5..db360c456d672 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -44,7 +44,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; public class MicrosoftGraphAuthzRealm extends Realm { diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index 2e0d3fe9513a4..a6d3605bd1a69 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -68,7 +68,13 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { new 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); + private static final MicrosoftGraphHttpFixture graphFixture = new MicrosoftGraphHttpFixture( + TENANT_ID, + CLIENT_ID, + CLIENT_SECRET, + TEST_USERS, + 3 + ); public static ElasticsearchCluster cluster = initTestCluster(); From f3865b2258253a86603acf4509d262da1b50eddc Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Tue, 3 Jun 2025 14:46:20 +0100 Subject: [PATCH 49/66] address review comments --- .../internal/dependencies/patches/Utils.java | 8 +- .../microsoft/MicrosoftGraphAuthzRealm.java | 41 +- .../MicrosoftGraphAuthzRealmTests.java | 443 +++++++++++++----- .../MicrosoftGraphAuthzPluginIT.java | 39 +- .../microsoft/MicrosoftGraphHttpFixture.java | 2 + .../security/authz/microsoft/TestUser.java | 12 - 6 files changed, 369 insertions(+), 176 deletions(-) delete mode 100644 x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java index 6224b8ab0430d..a53020e2c3b64 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java @@ -23,7 +23,9 @@ import java.util.Enumeration; import java.util.HexFormat; import java.util.Locale; +import java.util.Map; import java.util.function.Function; +import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; @@ -110,11 +112,7 @@ public static void patchJar(File inputFile, File outputFile, Collection void require(Setting.AffixSetting setting) { + private static void validate(RealmConfig config) { + require(config, MicrosoftGraphAuthzRealmSettings.CLIENT_ID); + require(config, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); + require(config, MicrosoftGraphAuthzRealmSettings.TENANT_ID); + } + + private static void require(RealmConfig config, Setting.AffixSetting setting) { final var value = config.getSetting(setting); - if (value.toString().isEmpty()) { + if (value.isEmpty()) { throw new SettingsException("The configuration setting [" + RealmSettings.getFullSettingKey(config, setting) + "] is required"); } } @@ -146,14 +141,16 @@ public void lookupUser(String principal, ActionListener listener) { logger.trace("Authorized user from Microsoft Graph {}", user); l.onResponse(user); })); - } catch (ReflectiveOperationException | ODataError e) { - logger.error("failed to authenticate with realm", e); + } catch (Exception e) { + logger.error(Strings.format("Failed to authorize [{}] with MS Graph realm", principal), e); listener.onFailure(e); } }); } - private GraphServiceClient buildClient(SecureString clientSecret) { + private static GraphServiceClient buildClient(RealmConfig config) { + final var clientSecret = config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); + final var credentialProviderBuilder = new ClientSecretCredentialBuilder().clientId( config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_ID) ) @@ -189,7 +186,7 @@ private List fetchGroupMembership(GraphServiceClient client, String user var groupMembership = client.users().byUserId(userId).transitiveMemberOf().graphGroup().get(requestConfig -> { requestConfig.queryParameters.select = new String[] { "id" }; - requestConfig.queryParameters.top = 999; + requestConfig.queryParameters.top = PAGE_SIZE; }); var pageIterator = new PageIterator.Builder().client(client) @@ -197,7 +194,7 @@ private List fetchGroupMembership(GraphServiceClient client, String user .collectionPageFactory(GroupCollectionResponse::createFromDiscriminatorValue) .requestConfigurator(requestInfo -> { requestInfo.addQueryParameter("%24select", new String[] { "id" }); - requestInfo.addQueryParameter("%24top", "999"); + requestInfo.addQueryParameter("%24top", String.valueOf(PAGE_SIZE)); return requestInfo; }) .processPageItemCallback(group -> { diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index 80180ac91b040..f45472633d71b 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -20,25 +20,35 @@ 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.List; +import java.util.Arrays; import java.util.Set; import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey; @@ -69,6 +79,37 @@ public class MicrosoftGraphAuthzRealmTests extends ESTestCase { 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(); @@ -78,128 +119,117 @@ public void tearDown() throws Exception { public void testLookupUser() { final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName)); - final var realmSettings = Settings.builder() - .put(globalSettings) - .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) - .build(); + 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 userRequestBuilder = mock(UsersRequestBuilder.class); - final var userItemRequestBuilder = mock(UserItemRequestBuilder.class); - final var msUser = new com.microsoft.graph.models.User(); - msUser.setDisplayName(name); - msUser.setMail(email); - - when(client.users()).thenReturn(userRequestBuilder); - when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); - when(userItemRequestBuilder.get(any())).thenReturn(msUser); + when(client.getRequestAdapter()).thenReturn(mock(RequestAdapter.class)); - final var memberOfRequestBuilder = mock(TransitiveMemberOfRequestBuilder.class); - final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); - final var group = new Group(); - group.setId(groupId); - final var groupMembership = new GroupCollectionResponse(); - groupMembership.setValue(List.of(group)); + final var userRequestBuilder = mockGetUser(client); + when(userRequestBuilder.get(any())).thenReturn(user(name, email)); - when(userItemRequestBuilder.transitiveMemberOf()).thenReturn(memberOfRequestBuilder); - when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); - when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership); + final var graphGroupRequestBuilder = mockGetGroupMembership(userRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership(groupId)); - final var licenseState = MockLicenseState.createMock(); - when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); + final var licenseState = mockLicense(true); final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); final var future = new PlainActionFuture(); 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)); + + 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 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 = Settings.builder() - .put(globalSettings) - .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) - .build(); + 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 userRequestBuilder = mock(UsersRequestBuilder.class); - final var userItemRequestBuilder = mock(UserItemRequestBuilder.class); - final var graphError = new ODataError(); - final var error = new MainError(); - error.setCode("badRequest"); - error.setMessage("bad stuff happened"); - graphError.setError(error); - - when(client.users()).thenReturn(userRequestBuilder); - when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); - when(userItemRequestBuilder.get(any())).thenThrow(graphError); + final var userItemRequestBuilder = mockGetUser(client); + when(userItemRequestBuilder.get(any())).thenThrow(graphError("bad stuff happened")); - final var licenseState = MockLicenseState.createMock(); - when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); + final var licenseState = mockLicense(true); final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); final var future = new PlainActionFuture(); - realm.lookupUser(username, future); - final var thrown = assertThrows(ODataError.class, future::actionGet); - assertThat(thrown.getMessage(), equalTo("bad stuff happened")); + + try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) { + mockLog.addExpectation(new MockLog.SeenEventExpectation( + "Log exception", + MicrosoftGraphAuthzRealm.class.getName(), + Level.ERROR, + Strings.format("Failed to authorize [{}] 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 = Settings.builder() - .put(globalSettings) - .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) - .build(); + 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); + when(client.getRequestAdapter()).thenReturn(mock(RequestAdapter.class)); - final var userRequestBuilder = mock(UsersRequestBuilder.class); - final var userItemRequestBuilder = mock(UserItemRequestBuilder.class); - final var msUser = new com.microsoft.graph.models.User(); - msUser.setDisplayName(name); - msUser.setMail(email); + final var userRequestBuilder = mockGetUser(client); + when(userRequestBuilder.get(any())).thenReturn(user(name, email)); - when(client.users()).thenReturn(userRequestBuilder); - when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); - when(userItemRequestBuilder.get(any())).thenReturn(msUser); + final var graphGroupRequestBuilder = mockGetGroupMembership(userRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenThrow(graphError("bad stuff happened")); - final var memberOfRequestBuilder = mock(TransitiveMemberOfRequestBuilder.class); - final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); - final var graphError = new ODataError(); - final var error = new MainError(); - error.setCode("badRequest"); - error.setMessage("bad stuff happened"); - graphError.setError(error); - - when(userItemRequestBuilder.transitiveMemberOf()).thenReturn(memberOfRequestBuilder); - when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); - when(graphGroupRequestBuilder.get(any())).thenThrow(graphError); - - final var licenseState = MockLicenseState.createMock(); - when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); + final var licenseState = mockLicense(true); final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); final var future = new PlainActionFuture(); - realm.lookupUser(username, future); - final var thrown = assertThrows(ODataError.class, future::actionGet); - assertThat(thrown.getMessage(), equalTo("bad stuff happened")); + + try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) { + mockLog.addExpectation(new MockLog.SeenEventExpectation( + "Log exception", + MicrosoftGraphAuthzRealm.class.getName(), + Level.ERROR, + Strings.format("Failed to authorize [{}] 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() { @@ -208,52 +238,29 @@ public void testGroupMembershipPagination() { final var roleMapper = mockRoleMapper(Set.of(groupId, groupId2, groupId3), Set.of(roleName)); - final var realmSettings = Settings.builder() - .put(globalSettings) - .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) - .build(); + 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 userRequestBuilder = mock(UsersRequestBuilder.class); - final var userItemRequestBuilder = mock(UserItemRequestBuilder.class); - final var msUser = new com.microsoft.graph.models.User(); - msUser.setDisplayName(name); - msUser.setMail(email); - - when(client.users()).thenReturn(userRequestBuilder); - when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); - when(userItemRequestBuilder.get(any())).thenReturn(msUser); + final var userItemRequestBuilder = mockGetUser(client); + when(userItemRequestBuilder.get(any())).thenReturn(user(name, email)); - final var memberOfRequestBuilder = mock(TransitiveMemberOfRequestBuilder.class); - final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); - final var group1 = new Group(); - group1.setId(groupId); - final var groupMembership1 = new GroupCollectionResponse(); - groupMembership1.setValue(List.of(group1)); + final var groupMembership1 = groupMembership(groupId); groupMembership1.setOdataNextLink("http://localhost:12345/page2"); - final var group2 = new Group(); - group2.setId(groupId2); - final var groupMembership2 = new GroupCollectionResponse(); - groupMembership2.setValue(List.of(group2)); + final var groupMembership2 = groupMembership(groupId2); groupMembership2.setOdataNextLink("http://localhost:12345/page3"); - final var group3 = new Group(); - group3.setId(groupId3); - final var groupMembership3 = new GroupCollectionResponse(); - groupMembership3.setValue(List.of(group3)); + final var groupMembership3 = groupMembership(groupId3); - when(userItemRequestBuilder.transitiveMemberOf()).thenReturn(memberOfRequestBuilder); - when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); + final var graphGroupRequestBuilder = mockGetGroupMembership(userItemRequestBuilder); when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership1); when(requestAdapter.send(any(), any(), any())).thenReturn(groupMembership2, groupMembership3); - final var licenseState = MockLicenseState.createMock(); - when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(true); + final var licenseState = mockLicense(true); final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); final var future = new PlainActionFuture(); @@ -267,16 +274,12 @@ public void testGroupMembershipPagination() { public void testLicenseCheck() { final var roleMapper = mock(UserRoleMapper.class); - final var realmSettings = Settings.builder() - .put(globalSettings) - .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) - .build(); + final var realmSettings = realmSettings().build(); final var config = new RealmConfig(realmId, realmSettings, env, threadContext); final var client = mock(GraphServiceClient.class); - final var licenseState = MockLicenseState.createMock(); - when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(false); + final var licenseState = mockLicense(false); final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); final var future = new PlainActionFuture(); @@ -285,6 +288,137 @@ public void testLicenseCheck() { 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>(); + realm.authenticate(fakeToken, future); + final var result = future.actionGet(); + assertThat(result, equalTo(AuthenticationResult.notHandled())); + } + private UserRoleMapper mockRoleMapper(Set expectedGroups, Set rolesToReturn) { final var roleMapper = mock(UserRoleMapper.class); doAnswer(invocation -> { @@ -298,4 +432,71 @@ private UserRoleMapper mockRoleMapper(Set expectedGroups, Set ro 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; + } } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index a6d3605bd1a69..713d243416d22 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -56,16 +56,16 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { private static final String USERNAME = "Thor"; private static final String EXPECTED_GROUP = "test_group"; - private static final List TEST_USERS = List.of( - new TestUser( + private static final List 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 TestUser("User2", "User 2", "user2@example.com", List.of(EXPECTED_GROUP), List.of("microsoft_graph_user")), - new TestUser("User3", "User 3", "user3@example.com", List.of(), List.of()) + 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( @@ -184,7 +184,7 @@ protected boolean shouldConfigureProjects() { public void testAuthenticationSuccessful() throws Exception { final var listener = new PlainActionFuture>(); - samlAuthWithMicrosoftGraphAuthz(USERNAME, getSamlAssertionJsonBodyString(USERNAME), listener); + samlAuthWithMicrosoftGraphAuthz(getSamlAssertionJsonBodyString(USERNAME), listener); final var resp = listener.get(); List roles = new XContentTestUtils.JsonMapView(resp).get("authentication.roles"); assertThat(resp.get("username"), equalTo(USERNAME)); @@ -193,20 +193,27 @@ public void testAuthenticationSuccessful() throws Exception { } public void testConcurrentAuthentication() throws Exception { + final var concurrentLogins = 3; + final var resultsListener = new PlainActionFuture>>(); - final var groupedListener = new GroupedActionListener<>(TEST_USERS.size(), resultsListener); - for (var user : TEST_USERS) { - samlAuthWithMicrosoftGraphAuthz(user.username(), getSamlAssertionJsonBodyString(user.username()), groupedListener); + 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 responses = resultsListener.get(); + final var allResponses = resultsListener.get(); - assertThat(responses.size(), equalTo(TEST_USERS.size())); + assertThat(allResponses.size(), equalTo(TEST_USERS.size() * concurrentLogins)); for (var user : TEST_USERS) { - var response = responses.stream().filter(r -> r.get("username").equals(user.username())).findFirst(); - assertTrue(response.isPresent()); - final List roles = new XContentTestUtils.JsonMapView(response.get()).get("authentication.roles"); - assertThat(roles, equalTo(user.roles())); - assertThat(ObjectPath.evaluate(response.get(), "authentication.authentication_realm.name"), equalTo("saml1")); + var userResponses = allResponses.stream().filter(r -> r.get("username").equals(user.username())).toList(); + assertThat(userResponses.size(), equalTo(concurrentLogins)); + + for (var response : userResponses) { + final List roles = new XContentTestUtils.JsonMapView(response).get("authentication.roles"); + assertThat(roles, equalTo(user.roles())); + assertThat(ObjectPath.evaluate(response, "authentication.authentication_realm.name"), equalTo("saml1")); + } } } @@ -224,7 +231,7 @@ private String getSamlAssertionJsonBodyString(String username) throws Exception return Strings.toString(JsonXContent.contentBuilder().map(body)); } - private void samlAuthWithMicrosoftGraphAuthz(String username, String samlAssertion, ActionListener> listener) { + private void samlAuthWithMicrosoftGraphAuthz(String samlAssertion, ActionListener> listener) { var req = new Request("POST", "_security/saml/authenticate"); req.setJsonEntity(samlAssertion); client().performRequestAsync(req, ActionTestUtils.wrapAsRestResponseListener(listener.map(ESRestTestCase::entityAsMap))); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java index 1f98c79df7a1f..6a14708417338 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java @@ -281,4 +281,6 @@ private void graphError(HttpExchange exchange, RestStatus statusCode, String mes exchange.close(); } + + public record TestUser(String username, String displayName, String email, List groups, List roles) {} } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java deleted file mode 100644 index 2da3d96233287..0000000000000 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/TestUser.java +++ /dev/null @@ -1,12 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.security.authz.microsoft; - -import java.util.List; - -public record TestUser(String username, String displayName, String email, List groups, List roles) {} From 2ec60f60b7deb5ba0a47afec6276dfbfa7bfe05f Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 3 Jun 2025 13:55:41 +0000 Subject: [PATCH 50/66] [CI] Auto commit changes from spotless --- .../internal/dependencies/patches/Utils.java | 3 - .../MicrosoftGraphAuthzRealmTests.java | 58 +++++++++++-------- .../MicrosoftGraphAuthzPluginIT.java | 8 ++- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java index a53020e2c3b64..068c34ffed94d 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java @@ -23,13 +23,10 @@ import java.util.Enumeration; import java.util.HexFormat; import java.util.Locale; -import java.util.Map; import java.util.function.Function; -import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; import java.util.stream.Collectors; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index f45472633d71b..3b8863c435208 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -138,19 +138,23 @@ public void testLookupUser() { realm.lookupUser(username, future); 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) - )); + 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 user = future.actionGet(); assertThat(user.principal(), equalTo(username)); @@ -181,12 +185,14 @@ public void testHandleGetUserPropertiesError() { final var future = new PlainActionFuture(); try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) { - mockLog.addExpectation(new MockLog.SeenEventExpectation( - "Log exception", - MicrosoftGraphAuthzRealm.class.getName(), - Level.ERROR, - Strings.format("Failed to authorize [{}] with MS Graph realm", username) - )); + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "Log exception", + MicrosoftGraphAuthzRealm.class.getName(), + Level.ERROR, + Strings.format("Failed to authorize [{}] with MS Graph realm", username) + ) + ); realm.lookupUser(username, future); final var thrown = assertThrows(ODataError.class, future::actionGet); @@ -217,12 +223,14 @@ public void testHandleGetGroupMembershipError() { final var future = new PlainActionFuture(); try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) { - mockLog.addExpectation(new MockLog.SeenEventExpectation( - "Log exception", - MicrosoftGraphAuthzRealm.class.getName(), - Level.ERROR, - Strings.format("Failed to authorize [{}] with MS Graph realm", username) - )); + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "Log exception", + MicrosoftGraphAuthzRealm.class.getName(), + Level.ERROR, + Strings.format("Failed to authorize [{}] with MS Graph realm", username) + ) + ); realm.lookupUser(username, future); final var thrown = assertThrows(ODataError.class, future::actionGet); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index 713d243416d22..929b348161b40 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -64,7 +64,13 @@ public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { 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( + "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()) ); From 65a751c3c8f8ccd9c0737724a50eefe7c8793754 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 5 Jun 2025 12:11:01 +0100 Subject: [PATCH 51/66] set default request timeout of 10 seconds --- plugins/microsoft-graph-authz/build.gradle | 2 +- .../src/main/java/module-info.java | 2 ++ .../microsoft/MicrosoftGraphAuthzRealm.java | 23 +++++++++++++++++-- .../MicrosoftGraphAuthzRealmSettings.java | 7 ++++++ .../MicrosoftGraphAuthzPluginIT.java | 11 +++++++++ .../microsoft/MicrosoftGraphHttpFixture.java | 14 +++++++++-- 6 files changed, 54 insertions(+), 5 deletions(-) diff --git a/plugins/microsoft-graph-authz/build.gradle b/plugins/microsoft-graph-authz/build.gradle index 152b3f3822557..5d46638921450 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/plugins/microsoft-graph-authz/build.gradle @@ -71,7 +71,7 @@ dependencies { 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" - runtimeOnly "com.azure:azure-core-http-okhttp:1.12.10" + 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" diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/plugins/microsoft-graph-authz/src/main/java/module-info.java index 66b91d780166e..2914f63da36bc 100644 --- a/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ b/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -22,6 +22,8 @@ requires com.microsoft.graph.core; requires kotlin.stdlib; requires com.google.gson; + requires okhttp3; + requires com.azure.core.http.okhttp; provides org.elasticsearch.xpack.core.security.SecurityExtension with MicrosoftGraphAuthzPlugin; } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 89651f0513863..66e077a82fa5b 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -9,6 +9,10 @@ package org.elasticsearch.xpack.security.authz.microsoft; +import com.azure.core.http.HttpClient; +import com.azure.core.http.okhttp.OkHttpAsyncHttpClientBuilder; +import com.azure.core.http.policy.RetryPolicy; +import com.azure.core.util.HttpClientOptions; import com.azure.identity.ClientSecretCredentialBuilder; import com.microsoft.graph.core.requests.BaseGraphRequestAdapter; import com.microsoft.graph.core.tasks.PageIterator; @@ -17,6 +21,10 @@ import com.microsoft.graph.serviceclient.GraphServiceClient; import com.microsoft.kiota.authentication.AzureIdentityAuthenticationProvider; +import com.microsoft.kiota.http.middleware.RetryHandler; + +import okhttp3.OkHttpClient; + import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Setting; @@ -39,6 +47,7 @@ import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.core.security.user.User; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -151,12 +160,21 @@ public void lookupUser(String principal, ActionListener listener) { 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)); + .authorityHost(config.getSetting(MicrosoftGraphAuthzRealmSettings.ACCESS_TOKEN_HOST)) + .httpClient(new OkHttpAsyncHttpClientBuilder(httpClient).build()) + .enableUnsafeSupportLogging() + .enableAccountIdentifierLogging(); if (DISABLE_INSTANCE_DISCOVERY) { credentialProviderBuilder.disableInstanceDiscovery(); @@ -166,7 +184,8 @@ private static GraphServiceClient buildClient(RealmConfig config) { return new GraphServiceClient( new BaseGraphRequestAdapter( new AzureIdentityAuthenticationProvider(credentialProvider, Strings.EMPTY_ARRAY, "https://graph.microsoft.com/.default"), - config.getSetting(MicrosoftGraphAuthzRealmSettings.API_HOST) + config.getSetting(MicrosoftGraphAuthzRealmSettings.API_HOST), + httpClient ) ); } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java index 5042c8342b8d6..e2fe2befe6aa7 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java @@ -11,6 +11,7 @@ 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; @@ -45,6 +46,12 @@ public class MicrosoftGraphAuthzRealmSettings { key -> Setting.simpleString(key, "https://graph.microsoft.com/v1.0", Setting.Property.NodeScope) ); + public static final Setting.AffixSetting HTTP_REQUEST_TIMEOUT = Setting.affixKeySetting( + RealmSettings.realmSettingPrefix(REALM_TYPE), + "http_request_timeout", + key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(10), Setting.Property.NodeScope) + ); + public static List> getSettings() { var settings = new ArrayList>(RealmSettings.getStandardSettings(REALM_TYPE)); settings.add(CLIENT_ID); diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index 929b348161b40..d003e9dec9c4a 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -12,6 +12,7 @@ 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; @@ -183,6 +184,16 @@ protected Settings restClientSettings() { 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; diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java index 6a14708417338..d557fd7123332 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java @@ -110,7 +110,12 @@ public String getBaseUrl() { private void registerGetAccessTokenHandler() { server.createContext("/" + tenantId + "/oauth2/v2.0/token", exchange -> { logger.info("Received access token request"); - loginCount.incrementAndGet(); + 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"); @@ -214,7 +219,12 @@ private void registerGetUserMembershipHandler(TestUser user) { server.createContext("/v1.0/users/" + user.username() + "/transitiveMemberOf", exchange -> { logger.info("Received get user membership request [{}]", exchange.getRequestURI()); - getGroupMembershipCount.incrementAndGet(); + 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"); From 5d9c3589e413d5bece91293c3accecc726692c5d Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Thu, 5 Jun 2025 11:20:32 +0000 Subject: [PATCH 52/66] [CI] Auto commit changes from spotless --- .../authz/microsoft/MicrosoftGraphAuthzRealm.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 66e077a82fa5b..303e1ba4f7e7a 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -9,10 +9,9 @@ package org.elasticsearch.xpack.security.authz.microsoft; -import com.azure.core.http.HttpClient; +import okhttp3.OkHttpClient; + import com.azure.core.http.okhttp.OkHttpAsyncHttpClientBuilder; -import com.azure.core.http.policy.RetryPolicy; -import com.azure.core.util.HttpClientOptions; import com.azure.identity.ClientSecretCredentialBuilder; import com.microsoft.graph.core.requests.BaseGraphRequestAdapter; import com.microsoft.graph.core.tasks.PageIterator; @@ -20,11 +19,8 @@ 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 okhttp3.OkHttpClient; - import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Setting; @@ -161,8 +157,7 @@ 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())) + final var httpClient = new OkHttpClient.Builder().callTimeout(Duration.ofSeconds(timeout.seconds())) .addInterceptor(new RetryHandler()) .build(); From b8581e258df091a74edf8b653f7ee47a0587a0e1 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 5 Jun 2025 14:55:37 +0100 Subject: [PATCH 53/66] cancel graph authorization tasks that are pending too long --- .../src/main/java/module-info.java | 1 + .../microsoft/MicrosoftGraphAuthzRealm.java | 66 +++++++++------- .../MicrosoftGraphAuthzRealmSettings.java | 6 ++ .../security/support/CancellableRunnable.java | 76 +++++++++++++++++++ .../support/CancellableRunnableTests.java} | 15 ++-- .../xpack/security/authc/ldap/LdapRealm.java | 70 +---------------- 6 files changed, 133 insertions(+), 101 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/CancellableRunnable.java rename x-pack/plugin/{security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/CancellableLdapRunnableTests.java => core/src/test/java/org/elasticsearch/xpack/core/security/support/CancellableRunnableTests.java} (84%) diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/plugins/microsoft-graph-authz/src/main/java/module-info.java index 2914f63da36bc..58bec3143bcaf 100644 --- a/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ b/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -24,6 +24,7 @@ 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; } diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index 303e1ba4f7e7a..a92dd11bc7084 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -21,18 +21,20 @@ 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.logging.LogManager; -import org.elasticsearch.logging.Logger; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.core.XPackPlugin; import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; @@ -41,6 +43,7 @@ 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; @@ -69,6 +72,7 @@ public class MicrosoftGraphAuthzRealm extends Realm { 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); @@ -90,6 +94,7 @@ public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config, T this.client = client; this.licenseState = licenseState; this.threadPool = threadPool; + this.executionTimeout = config.getSetting(MicrosoftGraphAuthzRealmSettings.EXECUTION_TIMEOUT); } private static void validate(RealmConfig config) { @@ -127,30 +132,39 @@ public void lookupUser(String principal, ActionListener listener) { return; } - threadPool.generic().execute(() -> { - 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 [{}] with MS Graph realm", principal), e); - listener.onFailure(e); - } - }); + 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 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 [{}] with MS Graph realm", principal), e); + listener.onFailure(e); + } } private static GraphServiceClient buildClient(RealmConfig config) { diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java index e2fe2befe6aa7..cb1240094978e 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java @@ -52,6 +52,12 @@ public class MicrosoftGraphAuthzRealmSettings { key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(10), Setting.Property.NodeScope) ); + public static final Setting.AffixSetting EXECUTION_TIMEOUT = Setting.affixKeySetting( + RealmSettings.realmSettingPrefix(REALM_TYPE), + "execution_timeout", + key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(30), Setting.Property.NodeScope) + ); + public static List> getSettings() { var settings = new ArrayList>(RealmSettings.getStandardSettings(REALM_TYPE)); settings.add(CLIENT_ID); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/CancellableRunnable.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/CancellableRunnable.java new file mode 100644 index 0000000000000..f802923ce3c05 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/CancellableRunnable.java @@ -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 extends AbstractRunnable { + + private final Runnable in; + private final ActionListener listener; + private final Function defaultValue; + private final Logger logger; + private final AtomicReference state = new AtomicReference<>(RunnableState.AWAITING_EXECUTION); + + public CancellableRunnable(ActionListener listener, Function 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 + } +} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/CancellableLdapRunnableTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/support/CancellableRunnableTests.java similarity index 84% rename from x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/CancellableLdapRunnableTests.java rename to x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/support/CancellableRunnableTests.java index aa8e5610dbc23..ef96461b6be07 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/CancellableLdapRunnableTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/support/CancellableRunnableTests.java @@ -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.instanceOf; import static org.hamcrest.Matchers.sameInstance; -public class CancellableLdapRunnableTests extends ESTestCase { +public class CancellableRunnableTests extends ESTestCase { public void testTimingOutARunnable() { AtomicReference exceptionAtomicReference = new AtomicReference<>(); - final CancellableLdapRunnable runnable = new CancellableLdapRunnable<>(ActionListener.wrap(user -> { + final CancellableRunnable 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 void testTimingOutARunnable() { public void testCallTimeOutAfterRunning() { final AtomicBoolean ran = new AtomicBoolean(false); final AtomicBoolean listenerCalled = new AtomicBoolean(false); - final CancellableLdapRunnable runnable = new CancellableLdapRunnable<>(ActionListener.wrap(user -> { + final CancellableRunnable runnable = new CancellableRunnable<>(ActionListener.wrap(user -> { listenerCalled.set(true); throw new AssertionError("onResponse should not be called"); }, e -> { @@ -59,7 +58,7 @@ public void testCallTimeOutAfterRunning() { public void testRejectingExecution() { AtomicReference exceptionAtomicReference = new AtomicReference<>(); - final CancellableLdapRunnable runnable = new CancellableLdapRunnable<>(ActionListener.wrap(user -> { + final CancellableRunnable 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 void testTimeoutDuringExecution() throws InterruptedException { final CountDownLatch timeoutCalledLatch = new CountDownLatch(1); final CountDownLatch runningLatch = new CountDownLatch(1); final ActionListener listener = ActionTestUtils.assertNoFailureListener(user -> listenerCalledLatch.countDown()); - final CancellableLdapRunnable runnable = new CancellableLdapRunnable<>(listener, e -> null, () -> { + final CancellableRunnable runnable = new CancellableRunnable<>(listener, e -> null, () -> { runningLatch.countDown(); try { timeoutCalledLatch.await(); @@ -98,7 +97,7 @@ public void testExceptionInRunnable() { AtomicReference resultRef = new AtomicReference<>(); final ActionListener listener = ActionTestUtils.assertNoFailureListener(resultRef::set); String defaultValue = randomAlphaOfLengthBetween(2, 10); - final CancellableLdapRunnable runnable = new CancellableLdapRunnable<>(listener, e -> defaultValue, () -> { + final CancellableRunnable runnable = new CancellableRunnable<>(listener, e -> defaultValue, () -> { throw new RuntimeException("runnable intentionally failed"); }, logger); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java index 1d3c3bf5f0a15..abf26dc7953eb 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java @@ -8,13 +8,10 @@ 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; @@ -41,15 +38,14 @@ import org.elasticsearch.xpack.security.authc.support.DelegatedAuthorizationSupport; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; import org.elasticsearch.xpack.security.authc.support.mapper.CompositeRoleMapper; +import org.elasticsearch.xpack.core.security.support.CancellableRunnable; 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 @@ protected void doAuthenticate(UsernamePasswordToken token, ActionListener> cancellableLdapRunnable = new CancellableLdapRunnable<>( + final CancellableRunnable> cancellableLdapRunnable = new CancellableRunnable<>( listener, ex -> AuthenticationResult.unsuccessful("Authentication against realm [" + this.toString() + "] failed", ex), () -> sessionFactory.session( @@ -173,7 +169,7 @@ protected void doLookupUser(String username, ActionListener userActionList result -> userActionListener.onResponse(result.getValue()), userActionListener::onFailure ); - final CancellableLdapRunnable cancellableLdapRunnable = new CancellableLdapRunnable<>( + final CancellableRunnable cancellableLdapRunnable = new CancellableRunnable<>( userActionListener, e -> null, () -> sessionFactory.unauthenticatedSession( @@ -323,65 +319,5 @@ public void onFailure(Exception e) { } 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 extends AbstractRunnable { - - private final Runnable in; - private final ActionListener listener; - private final Function defaultValue; - private final Logger logger; - private final AtomicReference state = new AtomicReference<>(LdapRunnableState.AWAITING_EXECUTION); - - CancellableLdapRunnable(ActionListener listener, Function 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 - } } } From 26d2594cc650e14aee36a7a2aa0f18abd299fb57 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Thu, 5 Jun 2025 14:05:17 +0000 Subject: [PATCH 54/66] [CI] Auto commit changes from spotless --- .../security/authz/microsoft/MicrosoftGraphAuthzRealm.java | 7 +------ .../elasticsearch/xpack/security/authc/ldap/LdapRealm.java | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index a92dd11bc7084..a7bbac7ef6563 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -132,12 +132,7 @@ public void lookupUser(String principal, ActionListener listener) { return; } - final var runnable = new CancellableRunnable<>( - listener, - ex -> null, - () -> doLookupUser(principal, listener), - logger - ); + 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); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java index abf26dc7953eb..50a72ed4d0340 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java @@ -29,6 +29,7 @@ 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; @@ -38,7 +39,6 @@ import org.elasticsearch.xpack.security.authc.support.DelegatedAuthorizationSupport; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; import org.elasticsearch.xpack.security.authc.support.mapper.CompositeRoleMapper; -import org.elasticsearch.xpack.core.security.support.CancellableRunnable; import org.elasticsearch.xpack.security.support.ReloadableSecurityComponent; import java.util.HashMap; From 9decc758fa23f1eddaf2c284eb52bb273a740171 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 5 Jun 2025 16:25:18 +0100 Subject: [PATCH 55/66] fix string interpolator --- .../security/authz/microsoft/MicrosoftGraphAuthzRealm.java | 2 +- .../authz/microsoft/MicrosoftGraphAuthzRealmTests.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index a7bbac7ef6563..f305962be5f12 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -157,7 +157,7 @@ private void doLookupUser(String principal, ActionListener listener) { l.onResponse(user); })); } catch (Exception e) { - logger.error(Strings.format("Failed to authorize [{}] with MS Graph realm", principal), e); + logger.error(Strings.format("Failed to authorize [%s] with MS Graph realm", principal), e); listener.onFailure(e); } } diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index 3b8863c435208..b05967e7eedf2 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -72,7 +72,7 @@ public class MicrosoftGraphAuthzRealmTests extends ESTestCase { 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 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, @@ -190,7 +190,7 @@ public void testHandleGetUserPropertiesError() { "Log exception", MicrosoftGraphAuthzRealm.class.getName(), Level.ERROR, - Strings.format("Failed to authorize [{}] with MS Graph realm", username) + Strings.format("Failed to authorize [%s] with MS Graph realm", username) ) ); @@ -228,7 +228,7 @@ public void testHandleGetGroupMembershipError() { "Log exception", MicrosoftGraphAuthzRealm.class.getName(), Level.ERROR, - Strings.format("Failed to authorize [{}] with MS Graph realm", username) + Strings.format("Failed to authorize [%s] with MS Graph realm", username) ) ); From 612ad1fa0eec4cdca919b675a757de3c3038e46b Mon Sep 17 00:00:00 2001 From: Johannes Freden Jansson Date: Mon, 9 Jun 2025 10:18:14 +0200 Subject: [PATCH 56/66] fixup! Register missing settings --- .../authz/microsoft/MicrosoftGraphAuthzRealmSettings.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java index cb1240094978e..00971e4e99d48 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java +++ b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java @@ -65,6 +65,8 @@ public static List> getSettings() { settings.add(TENANT_ID); settings.add(ACCESS_TOKEN_HOST); settings.add(API_HOST); + settings.add(HTTP_REQUEST_TIMEOUT); + settings.add(EXECUTION_TIMEOUT); return settings; } From 35e9ce42ceec8aaa11fadd79c04500cda8349345 Mon Sep 17 00:00:00 2001 From: Johannes Freden Jansson Date: Mon, 9 Jun 2025 11:46:16 +0200 Subject: [PATCH 57/66] fixup! Test issue --- .../authz/microsoft/MicrosoftGraphAuthzRealmTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index b05967e7eedf2..d5e68a09f9481 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -135,9 +135,9 @@ public void testLookupUser() { final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); final var future = new PlainActionFuture(); - realm.lookupUser(username, future); - try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) { + realm.lookupUser(username, future); + mockLog.addExpectation( new MockLog.SeenEventExpectation( "Fetch user properties", From 4c2e54d6b4376d67a76364d30e226fe95a7bea88 Mon Sep 17 00:00:00 2001 From: Johannes Freden Jansson Date: Tue, 10 Jun 2025 13:31:40 +0200 Subject: [PATCH 58/66] fixup! Test --- .../MicrosoftGraphAuthzRealmTests.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index d5e68a09f9481..984a777d5a735 100644 --- a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -117,25 +117,25 @@ public void tearDown() throws Exception { } public void testLookupUser() { - final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName)); + try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) { + final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName)); - final var realmSettings = realmSettings().build(); + 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 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 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 graphGroupRequestBuilder = mockGetGroupMembership(userRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership(groupId)); - final var licenseState = mockLicense(true); + final var licenseState = mockLicense(true); - final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); - final var future = new PlainActionFuture(); - try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) { + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); + final var future = new PlainActionFuture(); realm.lookupUser(username, future); mockLog.addExpectation( From e5e12ca4a4a9fc95f6586f75a0319f51ff40cd6c Mon Sep 17 00:00:00 2001 From: Johannes Freden Jansson Date: Wed, 11 Jun 2025 10:22:06 +0200 Subject: [PATCH 59/66] Move plugin to new x-pack home --- settings.gradle | 3 ++- x-pack/extras/plugins/build.gradle | 27 +++++++++++++++++++ .../microsoft-graph-authz/build.gradle | 0 .../kiota-merged/build.gradle | 0 .../kiota-merged/licenses/kiota-LICENSE | 0 .../kiota-merged/licenses/kiota-NOTICE | 0 .../licenses/azure-LICENSE.txt | 0 .../licenses/azure-NOTICE.txt | 0 .../licenses/gson-LICENSE | 0 .../licenses/gson-NOTICE.txt | 0 .../licenses/jackson-LICENSE | 0 .../licenses/jackson-NOTICE | 0 .../licenses/jna-LICENSE.txt | 0 .../licenses/jna-NOTICE.txt | 0 .../licenses/kotlin-LICENSE.txt | 0 .../licenses/kotlin-NOTICE.txt | 0 .../licenses/microsoft-graph-LICENSE.txt | 0 .../licenses/microsoft-graph-NOTICE.txt | 0 .../licenses/msal4j-LICENSE.txt | 0 .../licenses/msal4j-NOTICE.txt | 0 .../licenses/okhttp-LICENSE.txt | 0 .../licenses/okhttp-NOTICE.txt | 0 .../licenses/okio-LICENSE.txt | 0 .../licenses/okio-NOTICE.txt | 0 .../licenses/opentelemetry-LICENSE.txt | 0 .../licenses/opentelemetry-NOTICE.txt | 0 .../licenses/reactive-streams-LICENSE.txt | 0 .../licenses/reactive-streams-NOTICE.txt | 0 .../licenses/reactor-core-LICENSE.txt | 0 .../licenses/reactor-core-NOTICE.txt | 0 .../licenses/std-uritemplate-LICENSE.txt | 0 .../licenses/std-uritemplate-NOTICE.txt | 0 .../src/main/java/module-info.java | 0 .../microsoft/MicrosoftGraphAuthzPlugin.java | 0 .../microsoft/MicrosoftGraphAuthzRealm.java | 0 .../MicrosoftGraphAuthzRealmSettings.java | 0 .../plugin-metadata/entitlement-policy.yaml | 0 ...arch.xpack.core.security.SecurityExtension | 0 .../MicrosoftGraphAuthzRealmTests.java | 0 39 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 x-pack/extras/plugins/build.gradle rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/build.gradle (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/kiota-merged/build.gradle (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/azure-LICENSE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/azure-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/gson-LICENSE (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/gson-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/jackson-LICENSE (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/jackson-NOTICE (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/jna-LICENSE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/jna-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/kotlin-LICENSE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/kotlin-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/microsoft-graph-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/msal4j-LICENSE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/msal4j-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/okhttp-LICENSE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/okhttp-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/okio-LICENSE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/okio-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/opentelemetry-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/reactive-streams-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/reactor-core-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/licenses/std-uritemplate-NOTICE.txt (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/src/main/java/module-info.java (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/src/main/resources/META-INF/services/org.elasticsearch.xpack.core.security.SecurityExtension (100%) rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java (100%) diff --git a/settings.gradle b/settings.gradle index 6bca35f40bb26..f3463efc5af38 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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' \ No newline at end of file +include 'qa:vector' diff --git a/x-pack/extras/plugins/build.gradle b/x-pack/extras/plugins/build.gradle new file mode 100644 index 0000000000000..614420a18f03d --- /dev/null +++ b/x-pack/extras/plugins/build.gradle @@ -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')) +} diff --git a/plugins/microsoft-graph-authz/build.gradle b/x-pack/extras/plugins/microsoft-graph-authz/build.gradle similarity index 100% rename from plugins/microsoft-graph-authz/build.gradle rename to x-pack/extras/plugins/microsoft-graph-authz/build.gradle diff --git a/plugins/microsoft-graph-authz/kiota-merged/build.gradle b/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/build.gradle similarity index 100% rename from plugins/microsoft-graph-authz/kiota-merged/build.gradle rename to x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/build.gradle diff --git a/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE b/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE similarity index 100% rename from plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE rename to x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE diff --git a/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE b/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE similarity index 100% rename from plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE rename to x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE diff --git a/plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/licenses/azure-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/azure-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/azure-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/azure-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/licenses/gson-LICENSE b/x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-LICENSE similarity index 100% rename from plugins/microsoft-graph-authz/licenses/gson-LICENSE rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-LICENSE diff --git a/plugins/microsoft-graph-authz/licenses/gson-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/gson-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/licenses/jackson-LICENSE b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-LICENSE similarity index 100% rename from plugins/microsoft-graph-authz/licenses/jackson-LICENSE rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-LICENSE diff --git a/plugins/microsoft-graph-authz/licenses/jackson-NOTICE b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-NOTICE similarity index 100% rename from plugins/microsoft-graph-authz/licenses/jackson-NOTICE rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-NOTICE diff --git a/plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/licenses/microsoft-graph-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/microsoft-graph-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/microsoft-graph-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/microsoft-graph-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/licenses/msal4j-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/msal4j-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/msal4j-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/msal4j-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/licenses/okhttp-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/okhttp-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/okhttp-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/okhttp-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/licenses/okio-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/okio-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/okio-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/okio-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/licenses/opentelemetry-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/opentelemetry-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/opentelemetry-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/opentelemetry-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/licenses/reactive-streams-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactive-streams-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/reactive-streams-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/reactive-streams-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/licenses/reactor-core-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactor-core-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/reactor-core-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/reactor-core-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt diff --git a/plugins/microsoft-graph-authz/licenses/std-uritemplate-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/std-uritemplate-NOTICE.txt similarity index 100% rename from plugins/microsoft-graph-authz/licenses/std-uritemplate-NOTICE.txt rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/std-uritemplate-NOTICE.txt diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/module-info.java similarity index 100% rename from plugins/microsoft-graph-authz/src/main/java/module-info.java rename to x-pack/extras/plugins/microsoft-graph-authz/src/main/java/module-info.java diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java similarity index 100% rename from plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java rename to x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java similarity index 100% rename from plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java rename to x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java similarity index 100% rename from plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java rename to x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java diff --git a/plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml b/x-pack/extras/plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml similarity index 100% rename from plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml rename to x-pack/extras/plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml diff --git a/plugins/microsoft-graph-authz/src/main/resources/META-INF/services/org.elasticsearch.xpack.core.security.SecurityExtension b/x-pack/extras/plugins/microsoft-graph-authz/src/main/resources/META-INF/services/org.elasticsearch.xpack.core.security.SecurityExtension similarity index 100% rename from plugins/microsoft-graph-authz/src/main/resources/META-INF/services/org.elasticsearch.xpack.core.security.SecurityExtension rename to x-pack/extras/plugins/microsoft-graph-authz/src/main/resources/META-INF/services/org.elasticsearch.xpack.core.security.SecurityExtension diff --git a/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java similarity index 100% rename from plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java rename to x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java From 35d2d625ae129311bdc433f8e82a5ca832ffe817 Mon Sep 17 00:00:00 2001 From: Johannes Freden Jansson Date: Wed, 11 Jun 2025 13:27:06 +0200 Subject: [PATCH 60/66] fixup! Code review comment --- distribution/docker/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index e993c46101458..e3f87117ea029 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -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" From 78388f2d0db11cd1c743afcf369d616825d0880b Mon Sep 17 00:00:00 2001 From: Johannes Freden Jansson Date: Wed, 11 Jun 2025 13:54:35 +0200 Subject: [PATCH 61/66] fixup! Add build.gradle --- x-pack/extras/build.gradle | 8 ++++++++ .../security/qa/microsoft-graph-authz-tests/build.gradle | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 x-pack/extras/build.gradle diff --git a/x-pack/extras/build.gradle b/x-pack/extras/build.gradle new file mode 100644 index 0000000000000..ad21076a9ca93 --- /dev/null +++ b/x-pack/extras/build.gradle @@ -0,0 +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". + */ diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle index 2b8a89e69251d..f949902c68079 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle @@ -4,7 +4,7 @@ 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") } From b134b335032c843c8b3f3f2f848bbf608e1e0381 Mon Sep 17 00:00:00 2001 From: Johannes Freden Jansson Date: Wed, 11 Jun 2025 14:06:02 +0200 Subject: [PATCH 62/66] fixup! File extension --- .../licenses/{gson-LICENSE => gson-LICENSE.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename x-pack/extras/plugins/microsoft-graph-authz/licenses/{gson-LICENSE => gson-LICENSE.txt} (100%) diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-LICENSE b/x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-LICENSE.txt similarity index 100% rename from x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-LICENSE rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-LICENSE.txt From b3a9ff980c3531f2e4ca6c82ffaa40d6749c7880 Mon Sep 17 00:00:00 2001 From: Johannes Freden Jansson Date: Wed, 11 Jun 2025 14:06:33 +0200 Subject: [PATCH 63/66] fixup! File extension --- .../licenses/{jackson-LICENSE => jackson-LICENSE.txt} | 0 .../licenses/{jackson-NOTICE => jackson-NOTICE.txt} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename x-pack/extras/plugins/microsoft-graph-authz/licenses/{jackson-LICENSE => jackson-LICENSE.txt} (100%) rename x-pack/extras/plugins/microsoft-graph-authz/licenses/{jackson-NOTICE => jackson-NOTICE.txt} (100%) diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-LICENSE b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-LICENSE.txt similarity index 100% rename from x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-LICENSE rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-LICENSE.txt diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-NOTICE b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-NOTICE.txt similarity index 100% rename from x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-NOTICE rename to x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-NOTICE.txt From 6a41e78c664e44b0785a41b3ba22ea70629ecac7 Mon Sep 17 00:00:00 2001 From: Johannes Freden Jansson Date: Wed, 11 Jun 2025 14:07:33 +0200 Subject: [PATCH 64/66] fixup! File extension --- .../kiota-merged/licenses/{kiota-LICENSE => kiota-LICENSE.txt} | 0 .../kiota-merged/licenses/{kiota-NOTICE => kiota-NOTICE.txt} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/{kiota-LICENSE => kiota-LICENSE.txt} (100%) rename x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/{kiota-NOTICE => kiota-NOTICE.txt} (100%) diff --git a/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE b/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE.txt similarity index 100% rename from x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE rename to x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE.txt diff --git a/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE b/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE.txt similarity index 100% rename from x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE rename to x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE.txt From bdf0784150a685a290d3e17b3488d0533e44af2b Mon Sep 17 00:00:00 2001 From: Johannes Freden Jansson Date: Wed, 11 Jun 2025 15:20:46 +0200 Subject: [PATCH 65/66] fixup! License headers --- .../microsoft-graph-authz/src/main/java/module-info.java | 8 +++----- .../authz/microsoft/MicrosoftGraphAuthzPlugin.java | 8 +++----- .../authz/microsoft/MicrosoftGraphAuthzRealm.java | 8 +++----- .../authz/microsoft/MicrosoftGraphAuthzRealmSettings.java | 8 +++----- .../authz/microsoft/MicrosoftGraphAuthzRealmTests.java | 8 +++----- 5 files changed, 15 insertions(+), 25 deletions(-) diff --git a/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/module-info.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/module-info.java index 58bec3143bcaf..e631b44f1feb4 100644 --- a/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -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. */ import org.elasticsearch.xpack.security.authz.microsoft.MicrosoftGraphAuthzPlugin; diff --git a/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java index 09663e5407561..8bc99c1772380 100644 --- a/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java @@ -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; diff --git a/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java index f305962be5f12..04f2b177cf018 100644 --- a/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -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; diff --git a/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java index 00971e4e99d48..a48e0e11bdef0 100644 --- a/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java @@ -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; diff --git a/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index 984a777d5a735..e1e69a804b365 100644 --- a/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -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; From 8a761153bbb9d975e1e7164ac0b528eb0c6b51a1 Mon Sep 17 00:00:00 2001 From: Johannes Freden Jansson Date: Thu, 12 Jun 2025 08:33:18 +0200 Subject: [PATCH 66/66] fixup! Race condition in test --- .../MicrosoftGraphAuthzRealmTests.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java index e1e69a804b365..2b1c1f8959b89 100644 --- a/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -116,6 +116,24 @@ public void tearDown() throws Exception { 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(); @@ -136,24 +154,6 @@ public void testLookupUser() { final var future = new PlainActionFuture(); realm.lookupUser(username, future); - 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 user = future.actionGet(); assertThat(user.principal(), equalTo(username)); assertThat(user.fullName(), equalTo(name));