From 86badda5c85ecdab4000cbd4aefd0772c3a7dd21 Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Sat, 14 Jun 2025 17:30:32 +0900 Subject: [PATCH 1/2] refactor: add explicit SCIM API headers for proper content negotiation Add Accept and X-GitHub-Api-Version headers to all SCIM API requests, and Content-Type header for requests with body, to ensure proper content negotiation and API version specification for GitHub SCIM endpoints. --- .../org/kohsuke/github/GHEnterpriseExt.java | 24 +++++++++++++++++++ .../org/kohsuke/github/GHOrganizationExt.java | 10 ++++++++ .../org/kohsuke/github/SCIMConstants.java | 17 +++++++++++++ .../org/kohsuke/github/SCIMSearchBuilder.java | 6 +++-- 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/kohsuke/github/SCIMConstants.java diff --git a/src/main/java/org/kohsuke/github/GHEnterpriseExt.java b/src/main/java/org/kohsuke/github/GHEnterpriseExt.java index 513a32d..f856023 100644 --- a/src/main/java/org/kohsuke/github/GHEnterpriseExt.java +++ b/src/main/java/org/kohsuke/github/GHEnterpriseExt.java @@ -26,6 +26,9 @@ public SCIMEMUUser createSCIMEMUUser(SCIMEMUUser newUser) throws IOException { try (InputStream inputStream = new ByteArrayInputStream(jsonBytes)) { SCIMEMUUser u = root.createRequest() .method("POST") + .withHeader(SCIMConstants.HEADER_CONTENT_TYPE, SCIMConstants.SCIM_CONTENT_TYPE) + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .with(inputStream) .withUrlPath(String.format("/scim/v2/enterprises/%s/Users", login)) .fetch(SCIMEMUUser.class); @@ -40,6 +43,9 @@ public SCIMEMUUser updateSCIMEMUUser(String scimUserId, SCIMPatchOperations oper try (InputStream inputStream = new ByteArrayInputStream(jsonBytes)) { SCIMEMUUser u = root.createRequest() .method("PATCH") + .withHeader(SCIMConstants.HEADER_CONTENT_TYPE, SCIMConstants.SCIM_CONTENT_TYPE) + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .with(inputStream) .withUrlPath(String.format("/scim/v2/enterprises/%s/Users/%s", login, scimUserId)) .fetch(SCIMEMUUser.class); @@ -49,6 +55,8 @@ public SCIMEMUUser updateSCIMEMUUser(String scimUserId, SCIMPatchOperations oper public SCIMEMUUser getSCIMEMUUser(String scimUserId) throws IOException { SCIMEMUUser u = root.createRequest() + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .withUrlPath(String.format("/scim/v2/enterprises/%s/Users/%s", login, scimUserId)) .fetch(SCIMEMUUser.class); return u; @@ -56,6 +64,8 @@ public SCIMEMUUser getSCIMEMUUser(String scimUserId) throws IOException { public SCIMEMUUser getSCIMEMUUserByUserName(String scimUserName) throws IOException { SCIMEMUUser u = root.createRequest() + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .withUrlPath(String.format("/scim/v2/enterprises/%s/Users?filter=userName eq \"%s\"", login, scimUserName)) .fetch(SCIMEMUUser.class); return u; @@ -78,6 +88,8 @@ public SCIMPagedSearchIterable listSCIMUsers(int pageSize, int page public void deleteSCIMUser(String scimUserId) throws IOException { root.createRequest() .method("DELETE") + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .withUrlPath(String.format("/scim/v2/enterprises/%s/Users/%s", login, scimUserId)) .send(); } @@ -89,6 +101,9 @@ public SCIMEMUGroup createSCIMEMUGroup(SCIMEMUGroup newGroup) throws IOException try (InputStream inputStream = new ByteArrayInputStream(jsonBytes)) { SCIMEMUGroup g = root.createRequest() .method("POST") + .withHeader(SCIMConstants.HEADER_CONTENT_TYPE, SCIMConstants.SCIM_CONTENT_TYPE) + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .with(inputStream) .withUrlPath(String.format("/scim/v2/enterprises/%s/Groups", login)) .fetch(SCIMEMUGroup.class); @@ -103,6 +118,9 @@ public SCIMEMUGroup updateSCIMEMUGroup(String scimGroupId, SCIMPatchOperations o try (InputStream inputStream = new ByteArrayInputStream(jsonBytes)) { SCIMEMUGroup g = root.createRequest() .method("PATCH") + .withHeader(SCIMConstants.HEADER_CONTENT_TYPE, SCIMConstants.SCIM_CONTENT_TYPE) + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .with(inputStream) .withUrlPath(String.format("/scim/v2/enterprises/%s/Groups/%s", login, scimGroupId)) .fetch(SCIMEMUGroup.class); @@ -112,6 +130,8 @@ public SCIMEMUGroup updateSCIMEMUGroup(String scimGroupId, SCIMPatchOperations o public SCIMEMUGroup getSCIMEMUGroup(String scimGroupId) throws IOException { SCIMEMUGroup g = root.createRequest() + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .withUrlPath(String.format("/scim/v2/enterprises/%s/Groups/%s", login, scimGroupId)) .fetch(SCIMEMUGroup.class); return g; @@ -119,6 +139,8 @@ public SCIMEMUGroup getSCIMEMUGroup(String scimGroupId) throws IOException { public SCIMEMUGroup getSCIMEMUGroupByDisplayName(String scimGroupDisplayName) throws IOException { SCIMEMUGroup g = root.createRequest() + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .withUrlPath(String.format("/scim/v2/enterprises/%s/Groups?filter=displayName eq \"%s\"", login, scimGroupDisplayName)) .fetch(SCIMEMUGroup.class); return g; @@ -141,6 +163,8 @@ public SCIMPagedSearchIterable listSCIMGroups(int pageSize, int pa public void deleteSCIMGroup(String scimGroupId) throws IOException { root.createRequest() .method("DELETE") + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .withUrlPath(String.format("/scim/v2/enterprises/%s/Groups/%s", login, scimGroupId)) .send(); } diff --git a/src/main/java/org/kohsuke/github/GHOrganizationExt.java b/src/main/java/org/kohsuke/github/GHOrganizationExt.java index d385ddd..06ec05f 100644 --- a/src/main/java/org/kohsuke/github/GHOrganizationExt.java +++ b/src/main/java/org/kohsuke/github/GHOrganizationExt.java @@ -46,6 +46,8 @@ public SCIMUser createSCIMUser(SCIMUser newUser) throws IOException { SCIMUser u = root.createRequest() .method("POST") + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.GITHUB_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .with(map) .withUrlPath(String.format("/scim/v2/organizations/%s/Users", login)) .fetch(SCIMUser.class); @@ -81,6 +83,8 @@ public SCIMUser updateSCIMUser(String scimUserId, String scimUserName, String sc SCIMUser u = root.createRequest() .method("PATCH") + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.GITHUB_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .with(map) .withUrlPath(String.format("/scim/v2/organizations/%s/Users/%s", login, scimUserId)) .fetch(SCIMUser.class); @@ -89,6 +93,8 @@ public SCIMUser updateSCIMUser(String scimUserId, String scimUserName, String sc public SCIMUser getSCIMUser(String scimUserId) throws IOException { SCIMUser u = root.createRequest() + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.GITHUB_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .withUrlPath(String.format("/scim/v2/organizations/%s/Users/%s", login, scimUserId)) .fetch(SCIMUser.class); return u; @@ -96,6 +102,8 @@ public SCIMUser getSCIMUser(String scimUserId) throws IOException { public SCIMUser getSCIMUserByUserName(String scimUserName) throws IOException { SCIMUser u = root.createRequest() + .withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT) + .withHeader(SCIMConstants.GITHUB_API_VERSION, SCIMConstants.GITHUB_API_VERSION) .withUrlPath(String.format("/scim/v2/organizations/%s/Users?filter=userName eq \"%s\"", login, scimUserName)) .fetch(SCIMUser.class); return u; @@ -132,6 +140,8 @@ public GraphQLPagedSearchIterable extends GHQueryBuilder { this.organization = org; this.receiverType = receiverType; req.withUrlPath(getApiUrl()); - req.rateLimit(RateLimitTarget.SEARCH); + req.withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT); + req.withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION); } SCIMSearchBuilder(GitHub root, GHEnterpriseExt enterprise, Class> receiverType) { @@ -32,7 +33,8 @@ public abstract class SCIMSearchBuilder extends GHQueryBuilder { this.organization = null; this.receiverType = receiverType; req.withUrlPath(getApiUrl()); - req.rateLimit(RateLimitTarget.SEARCH); + req.withHeader(SCIMConstants.HEADER_ACCEPT, SCIMConstants.SCIM_ACCEPT); + req.withHeader(SCIMConstants.HEADER_API_VERSION, SCIMConstants.GITHUB_API_VERSION); } /** From e0a6e9733496cc77b496991bea9bf9afaf286d4d Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Sat, 14 Jun 2025 18:00:20 +0900 Subject: [PATCH 2/2] refactor: add required schemas field to SCIM resources for spec compliance Add schemas field to SCIMEMUUser, SCIMEMUGroup, and SCIMPatchOperations to ensure SCIM 2.0 specification compliance. Set appropriate schema URNs during resource creation and patch operations. --- src/main/java/org/kohsuke/github/GHEnterpriseExt.java | 4 ++++ src/main/java/org/kohsuke/github/SCIMConstants.java | 3 +++ src/main/java/org/kohsuke/github/SCIMEMUGroup.java | 3 +++ src/main/java/org/kohsuke/github/SCIMEMUUser.java | 3 +++ src/main/java/org/kohsuke/github/SCIMPatchOperations.java | 1 + 5 files changed, 14 insertions(+) diff --git a/src/main/java/org/kohsuke/github/GHEnterpriseExt.java b/src/main/java/org/kohsuke/github/GHEnterpriseExt.java index f856023..ef5a355 100644 --- a/src/main/java/org/kohsuke/github/GHEnterpriseExt.java +++ b/src/main/java/org/kohsuke/github/GHEnterpriseExt.java @@ -20,6 +20,8 @@ GHEnterpriseExt wrapUp(GitHub root) { } public SCIMEMUUser createSCIMEMUUser(SCIMEMUUser newUser) throws IOException { + newUser.schemas = new String[]{SCIMConstants.SCIM_USER_SCHEMA}; + String json = mapper.writeValueAsString(newUser); byte[] jsonBytes = json.getBytes(); @@ -95,6 +97,8 @@ public void deleteSCIMUser(String scimUserId) throws IOException { } public SCIMEMUGroup createSCIMEMUGroup(SCIMEMUGroup newGroup) throws IOException { + newGroup.schemas = new String[]{SCIMConstants.SCIM_GROUP_SCHEMA}; + String json = mapper.writeValueAsString(newGroup); byte[] jsonBytes = json.getBytes(); diff --git a/src/main/java/org/kohsuke/github/SCIMConstants.java b/src/main/java/org/kohsuke/github/SCIMConstants.java index fa6bc65..cfe5816 100644 --- a/src/main/java/org/kohsuke/github/SCIMConstants.java +++ b/src/main/java/org/kohsuke/github/SCIMConstants.java @@ -14,4 +14,7 @@ public final class SCIMConstants { public static final String HEADER_CONTENT_TYPE = "Content-Type"; public static final String HEADER_ACCEPT = "Accept"; public static final String HEADER_API_VERSION = "X-GitHub-Api-Version"; + + public static final String SCIM_USER_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:User"; + public static final String SCIM_GROUP_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Group"; } \ No newline at end of file diff --git a/src/main/java/org/kohsuke/github/SCIMEMUGroup.java b/src/main/java/org/kohsuke/github/SCIMEMUGroup.java index a6d95ff..85d949b 100644 --- a/src/main/java/org/kohsuke/github/SCIMEMUGroup.java +++ b/src/main/java/org/kohsuke/github/SCIMEMUGroup.java @@ -7,6 +7,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class SCIMEMUGroup { + @JsonProperty("schemas") + public String[] schemas; + @JsonProperty("meta") public SCIMMeta meta; diff --git a/src/main/java/org/kohsuke/github/SCIMEMUUser.java b/src/main/java/org/kohsuke/github/SCIMEMUUser.java index f76557c..18082f2 100644 --- a/src/main/java/org/kohsuke/github/SCIMEMUUser.java +++ b/src/main/java/org/kohsuke/github/SCIMEMUUser.java @@ -7,6 +7,9 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class SCIMEMUUser { + @JsonProperty("schemas") + public String[] schemas; + @JsonProperty("meta") public SCIMMeta meta; diff --git a/src/main/java/org/kohsuke/github/SCIMPatchOperations.java b/src/main/java/org/kohsuke/github/SCIMPatchOperations.java index 4099a3a..dff695a 100644 --- a/src/main/java/org/kohsuke/github/SCIMPatchOperations.java +++ b/src/main/java/org/kohsuke/github/SCIMPatchOperations.java @@ -12,6 +12,7 @@ public class SCIMPatchOperations { private static final String PATCH_OP = "urn:ietf:params:scim:api:messages:2.0:PatchOp"; + @JsonProperty("schemas") public List schemas = Collections.singletonList(PATCH_OP); @JsonProperty("Operations")