├── .github
├── CODEOWNERS
├── renovate.json
└── workflows
│ ├── cd.yaml
│ └── jenkins-security-scan.yml
├── .mvn
├── maven.config
└── extensions.xml
├── .vscode
└── settings.json
├── docs
└── images
│ ├── Capture_d’écran_2012-08-21_à_15.03.47.png
│ └── Capture_d’écran_2012-08-21_à_15.08.01.png
├── Jenkinsfile
├── LICENSE
├── src
├── test
│ └── java
│ │ └── com
│ │ └── cloudbees
│ │ └── jenkins
│ │ └── plugins
│ │ ├── AdditionalIdentityTest.java
│ │ ├── AdditionalIdentitiesTest.java
│ │ ├── AdditionalIdentitiesIntegrationTest.java
│ │ └── AdditionalIdentityResolverTest.java
└── main
│ ├── resources
│ ├── com
│ │ └── cloudbees
│ │ │ └── jenkins
│ │ │ └── plugins
│ │ │ └── AdditionalIdentities
│ │ │ ├── help-id.html
│ │ │ ├── help-realm.html
│ │ │ └── config.jelly
│ └── index.jelly
│ └── java
│ └── com
│ └── cloudbees
│ └── jenkins
│ └── plugins
│ ├── AdditionalIdentity.java
│ ├── AdditionalIdentities.java
│ └── AdditionalIdentityResolver.java
├── README.md
├── .gitignore
└── pom.xml
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @jenkinsci/additional-identities-plugin-developers
2 |
--------------------------------------------------------------------------------
/.mvn/maven.config:
--------------------------------------------------------------------------------
1 | -Pconsume-incrementals
2 | -Pmight-produce-incrementals
3 | -Dchangelist.format=%d.v%s
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "java.configuration.updateBuildConfiguration": "automatic",
3 | "java.compile.nullAnalysis.mode": "automatic"
4 | }
5 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["github>visualon/renovate-config//jenkins.json"]
4 | }
5 |
--------------------------------------------------------------------------------
/docs/images/Capture_d’écran_2012-08-21_à_15.03.47.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/additional-identities-plugin/main/docs/images/Capture_d’écran_2012-08-21_à_15.03.47.png
--------------------------------------------------------------------------------
/docs/images/Capture_d’écran_2012-08-21_à_15.08.01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/additional-identities-plugin/main/docs/images/Capture_d’écran_2012-08-21_à_15.08.01.png
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | /*
2 | See the documentation for more options:
3 | https://github.com/jenkins-infra/pipeline-library/
4 | */
5 | buildPlugin(
6 | useContainerAgent: true,
7 | configurations: [
8 | [platform: 'linux', jdk: 25],
9 | [platform: 'windows', jdk: 21],
10 | ])
11 |
--------------------------------------------------------------------------------
/.mvn/extensions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | io.jenkins.tools.incrementals
4 | git-changelist-maven-extension
5 | 1.13
6 |
7 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yaml:
--------------------------------------------------------------------------------
1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins
2 |
3 | name: cd
4 | on:
5 | workflow_dispatch:
6 | check_run:
7 | types:
8 | - completed
9 |
10 | permissions:
11 | checks: read
12 | contents: write
13 |
14 | jobs:
15 | maven-cd:
16 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@820822d414ad889e8d46a311a6706758d570d4de # v1.8.0
17 | secrets:
18 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
19 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }}
20 |
--------------------------------------------------------------------------------
/.github/workflows/jenkins-security-scan.yml:
--------------------------------------------------------------------------------
1 | name: Jenkins Security Scan
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | types: [ opened, synchronize, reopened ]
9 | workflow_dispatch:
10 |
11 | permissions:
12 | security-events: write
13 | contents: read
14 | actions: read
15 |
16 | jobs:
17 | security-scan:
18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@7ced5457323763df99467bb8546e7a30ce4ae5a5 # v2.2.1
19 | with:
20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate.
21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default.
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2012, CloudBees, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/test/java/com/cloudbees/jenkins/plugins/AdditionalIdentityTest.java:
--------------------------------------------------------------------------------
1 | package com.cloudbees.jenkins.plugins;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertNotNull;
5 |
6 | import org.junit.jupiter.api.DisplayName;
7 | import org.junit.jupiter.api.Test;
8 |
9 | class AdditionalIdentityTest {
10 |
11 | @Test
12 | @DisplayName("Should create an AdditionalIdentity with the correct values")
13 | void testConstructor() {
14 | // Given
15 | var id = "user123";
16 | var realm = "github";
17 |
18 | // When
19 | var identity = new AdditionalIdentity(id, realm);
20 |
21 | // Then
22 | assertNotNull(identity);
23 | assertEquals(id, identity.getId());
24 | assertEquals(realm, identity.getRealm());
25 | }
26 |
27 | @Test
28 | @DisplayName("Descriptor should have correct display name")
29 | void testDescriptor() {
30 | // Given
31 | var descriptor = new AdditionalIdentity.DescriptorImpl();
32 |
33 | // When
34 | var displayName = descriptor.getDisplayName();
35 |
36 | // Then
37 | assertEquals("Additional identity", displayName);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/resources/com/cloudbees/jenkins/plugins/AdditionalIdentities/help-id.html:
--------------------------------------------------------------------------------
1 |
24 |
25 | User identify as defined by external service, typically SCM ID.
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/main/resources/com/cloudbees/jenkins/plugins/AdditionalIdentities/help-realm.html:
--------------------------------------------------------------------------------
1 |
24 |
25 | Optional realm this identity applies to. Keep empty for global identity. For SCM identity,
26 | the repository domain name is used, assuming the SCM plugin handles realm.
27 |
28 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 | Additional user property for user identity on other systems. Jenkins then won't create duplicated users when such
27 | an identity is extracted during SCM changelog parsing, or other user-related events.
28 |
29 |
--------------------------------------------------------------------------------
/src/main/resources/com/cloudbees/jenkins/plugins/AdditionalIdentities/config.jelly:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/AdditionalIdentity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2012, CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package com.cloudbees.jenkins.plugins;
26 |
27 | import hudson.Extension;
28 | import hudson.model.AbstractDescribableImpl;
29 | import hudson.model.Descriptor;
30 | import org.kohsuke.stapler.DataBoundConstructor;
31 |
32 | /**
33 | * @author Nicolas De Loof
34 | */
35 | public class AdditionalIdentity extends AbstractDescribableImpl {
36 |
37 | final String id;
38 |
39 | final String realm;
40 |
41 | @DataBoundConstructor
42 | public AdditionalIdentity(String id, String realm) {
43 | this.id = id;
44 | this.realm = realm;
45 | }
46 |
47 | public String getId() {
48 | return id;
49 | }
50 |
51 | public String getRealm() {
52 | return realm;
53 | }
54 |
55 | @Extension
56 | public static class DescriptorImpl extends Descriptor {
57 |
58 | @Override
59 | public String getDisplayName() {
60 | return "Additional identity";
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/AdditionalIdentities.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2012, CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 | package com.cloudbees.jenkins.plugins;
25 |
26 | import hudson.Extension;
27 | import hudson.model.User;
28 | import hudson.model.UserProperty;
29 | import hudson.model.UserPropertyDescriptor;
30 | import java.util.List;
31 | import org.kohsuke.stapler.DataBoundConstructor;
32 |
33 | /**
34 | * @author Nicolas De Loof
35 | */
36 | public class AdditionalIdentities extends UserProperty {
37 |
38 | private final List identities;
39 |
40 | @DataBoundConstructor
41 | public AdditionalIdentities(List identities) {
42 | this.identities = identities;
43 | }
44 |
45 | public List getIdentities() {
46 | return identities;
47 | }
48 |
49 | @Extension
50 | public static class DescriptorImpl extends UserPropertyDescriptor {
51 |
52 | @Override
53 | public UserProperty newInstance(User user) {
54 | return null;
55 | }
56 |
57 | @Override
58 | public String getDisplayName() {
59 | return "Additional user identities";
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/test/java/com/cloudbees/jenkins/plugins/AdditionalIdentitiesTest.java:
--------------------------------------------------------------------------------
1 | package com.cloudbees.jenkins.plugins;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertNotNull;
5 | import static org.junit.jupiter.api.Assertions.assertNull;
6 | import static org.mockito.Mockito.mock;
7 |
8 | import hudson.model.User;
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.List;
12 | import org.junit.jupiter.api.DisplayName;
13 | import org.junit.jupiter.api.Test;
14 |
15 | class AdditionalIdentitiesTest {
16 |
17 | @Test
18 | @DisplayName("Should create AdditionalIdentities with the correct list of identities")
19 | void testConstructor() {
20 | // Given
21 | var identity1 = new AdditionalIdentity("user123", "github");
22 | var identity2 = new AdditionalIdentity("user456", "gitlab");
23 | var identities = Arrays.asList(identity1, identity2);
24 |
25 | // When
26 | var additionalIdentities = new AdditionalIdentities(identities);
27 |
28 | // Then
29 | assertNotNull(additionalIdentities);
30 | assertNotNull(additionalIdentities.getIdentities());
31 | assertEquals(2, additionalIdentities.getIdentities().size());
32 | assertEquals(identity1, additionalIdentities.getIdentities().get(0));
33 | assertEquals(identity2, additionalIdentities.getIdentities().get(1));
34 | }
35 |
36 | @Test
37 | @DisplayName("Should handle empty list of identities")
38 | void testConstructorWithEmptyList() {
39 | // Given
40 | List identities = new ArrayList<>();
41 |
42 | // When
43 | var additionalIdentities = new AdditionalIdentities(identities);
44 |
45 | // Then
46 | assertNotNull(additionalIdentities);
47 | assertNotNull(additionalIdentities.getIdentities());
48 | assertEquals(0, additionalIdentities.getIdentities().size());
49 | }
50 |
51 | @Test
52 | @DisplayName("Descriptor should have correct display name and return null for new instance")
53 | void testDescriptor() {
54 | // Given
55 | var descriptor = new AdditionalIdentities.DescriptorImpl();
56 | var user = mock(User.class);
57 |
58 | // When
59 | var displayName = descriptor.getDisplayName();
60 | var newInstance = descriptor.newInstance(user);
61 |
62 | // Then
63 | assertEquals("Additional user identities", displayName);
64 | assertNull(newInstance);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/jenkins/plugins/AdditionalIdentityResolver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2012, CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package com.cloudbees.jenkins.plugins;
26 |
27 | import static hudson.init.InitMilestone.PLUGINS_STARTED;
28 |
29 | import hudson.Extension;
30 | import hudson.init.Initializer;
31 | import hudson.model.User;
32 | import java.util.Map;
33 |
34 | /**
35 | * @author Nicolas De Loof
36 | */
37 | @Extension
38 | public class AdditionalIdentityResolver extends User.CanonicalIdResolver {
39 |
40 | @Override
41 | public String resolveCanonicalId(String id, Map context) {
42 |
43 | String realm = null;
44 |
45 | if (context != null) {
46 | realm = (String) context.get(User.CanonicalIdResolver.REALM);
47 | }
48 |
49 | for (User user : User.getAll()) {
50 | var identities = user.getProperty(AdditionalIdentities.class);
51 | if (identities == null) continue;
52 | for (AdditionalIdentity identity : identities.getIdentities()) {
53 | if (identity.id.equals(id)) {
54 | if (realm != null && identity.realm != null && !realm.contains(identity.realm)) {
55 | // realm don't match
56 | continue;
57 | }
58 | return user.getId();
59 | }
60 | }
61 | }
62 | return null;
63 | }
64 |
65 | @Initializer(before = PLUGINS_STARTED)
66 | public static void addAliases() {
67 | User.XSTREAM.addCompatibilityAlias(
68 | "com.cloudbees.jenkins.plugins.AdditionalItentity", AdditionalIdentity.class);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Additional Identities Plugin for Jenkins
2 |
3 | [](https://ci.jenkins.io/job/Plugins/job/additional-identities-plugin/job/main)
4 | [](https://ci.jenkins.io/job/Plugins/job/additional-identities-plugin/job/main/coverage)
5 | [](https://plugins.jenkins.io/additional-identities-plugin)
6 | [](https://github.com/jenkinsci/additional-identities-plugin/releases/latest)
7 | [](https://plugins.jenkins.io/additional-identities-plugin)
8 |
9 | This plugin allows user to configure additional identities in jenkins
10 | user database for external services, typically SCM repositories.
11 |
12 | ## Introduction
13 |
14 | As SCM plugin parse changelog, they have to map SCM committers IDs with
15 | jenkins user database. This results in many cases in user duplication,
16 | until you exactly have the same ID in jenkins and SCM.
17 |
18 | Jenkins 1.480 introduces an extension point to resolve jenkins user
19 | "canonical" ID when searching for user in Database by id or full name.
20 | This plugin uses this extension point to let user configure external
21 | identities as user properties.
22 |
23 | ## Usage
24 |
25 | On my Jenkins instance, I'm authenticated as "nicolas". As I want to use
26 | the same identity for commits on repositories, I can setup an additional
27 | identity for my account on googlecode :
28 | 
29 |
30 | With this additional identity set, Jenkins will be able to match the
31 | committer id in svn "" with the jenkins user
32 | "nicolas", and link the builds I contributed to in my user view :
33 |
34 | 
35 |
36 | ## Realms
37 |
38 | As for HTTP authentication, a realm can be set to restrict an identity
39 | to a set of network resources (i.e. domain names in most cases). The
40 | *realm* attribute can be used to restrict the sources user ID matching
41 | will apply. In most cases, this is a substring of the SCM repository
42 | URL. If not set, additional identity applies to all user lookups,
43 | whatever the id source is.
44 |
45 | This feature requires SCM plugins to be updated so that they compute
46 | host information form changelog and pass it to extension as
47 | [REALM](http://javadoc.jenkins-ci.org/hudson/model/User.CanonicalIdResolver.html#REALM) context
48 | attribute. Those plugins have been updated to support this advanced
49 | feature :
50 |
51 | - TO BE COMPLETED
52 |
53 | ## Tips
54 |
55 | git plugin uses user name, as set in git commit, as committer ID, so you
56 | don't need an additional identity, just ensure your git client is
57 | configured with correct user name set :
58 |
59 | ``` syntaxhighlighter-pre
60 | git config --global user.name "Your Full Name Comes Here"
61 | ```
62 |
63 | ## Changelog
64 |
65 | See GitHub [Releases](https://github.com/jenkinsci/additional-identities-plugin/releases) for newer changes.
66 |
67 | ### 1.1 (Oct 20 2015)
68 |
69 | - [JENKINS-28181](https://issues.jenkins-ci.org/browse/JENKINS-28181)
70 | NPE thrown in certain cases.
71 | - Internal class rename.
72 | - Missing descriptor error.
73 |
74 | ### 1.0
75 |
76 | - initial release
77 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## Visual Studio
34 | #################
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | *.suo
41 | *.user
42 | *.sln.docstates
43 |
44 | # Build results
45 |
46 | [Dd]ebug/
47 | [Rr]elease/
48 | x64/
49 | build/
50 | [Bb]in/
51 | [Oo]bj/
52 |
53 | # MSTest test Results
54 | [Tt]est[Rr]esult*/
55 | [Bb]uild[Ll]og.*
56 |
57 | *_i.c
58 | *_p.c
59 | *.ilk
60 | *.meta
61 | *.obj
62 | *.pch
63 | *.pdb
64 | *.pgc
65 | *.pgd
66 | *.rsp
67 | *.sbr
68 | *.tlb
69 | *.tli
70 | *.tlh
71 | *.tmp
72 | *.tmp_proj
73 | *.log
74 | *.vspscc
75 | *.vssscc
76 | .builds
77 | *.pidb
78 | *.log
79 | *.scc
80 |
81 | # Visual C++ cache files
82 | ipch/
83 | *.aps
84 | *.ncb
85 | *.opensdf
86 | *.sdf
87 | *.cachefile
88 |
89 | # Visual Studio profiler
90 | *.psess
91 | *.vsp
92 | *.vspx
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 |
101 | # TeamCity is a build add-in
102 | _TeamCity*
103 |
104 | # DotCover is a Code Coverage Tool
105 | *.dotCover
106 |
107 | # NCrunch
108 | *.ncrunch*
109 | .*crunch*.local.xml
110 |
111 | # Installshield output folder
112 | [Ee]xpress/
113 |
114 | # DocProject is a documentation generator add-in
115 | DocProject/buildhelp/
116 | DocProject/Help/*.HxT
117 | DocProject/Help/*.HxC
118 | DocProject/Help/*.hhc
119 | DocProject/Help/*.hhk
120 | DocProject/Help/*.hhp
121 | DocProject/Help/Html2
122 | DocProject/Help/html
123 |
124 | # Click-Once directory
125 | publish/
126 |
127 | # Publish Web Output
128 | *.Publish.xml
129 | *.pubxml
130 |
131 | # NuGet Packages Directory
132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
133 | #packages/
134 |
135 | # Windows Azure Build Output
136 | csx
137 | *.build.csdef
138 |
139 | # Windows Store app package directory
140 | AppPackages/
141 |
142 | # Others
143 | sql/
144 | *.Cache
145 | ClientBin/
146 | [Ss]tyle[Cc]op.*
147 | ~$*
148 | *~
149 | *.dbmdl
150 | *.[Pp]ublish.xml
151 | *.pfx
152 | *.publishsettings
153 |
154 | # RIA/Silverlight projects
155 | Generated_Code/
156 |
157 | # Backup & report files from converting an old project file to a newer
158 | # Visual Studio version. Backup files are not needed, because we have git ;-)
159 | _UpgradeReport_Files/
160 | Backup*/
161 | UpgradeLog*.XML
162 | UpgradeLog*.htm
163 |
164 | # SQL Server files
165 | App_Data/*.mdf
166 | App_Data/*.ldf
167 |
168 | #############
169 | ## Windows detritus
170 | #############
171 |
172 | # Windows image file caches
173 | Thumbs.db
174 | ehthumbs.db
175 |
176 | # Folder config file
177 | Desktop.ini
178 |
179 | # Recycle Bin used on file shares
180 | $RECYCLE.BIN/
181 |
182 | # Mac crap
183 | .DS_Store
184 |
185 |
186 | #############
187 | ## Python
188 | #############
189 |
190 | *.py[co]
191 |
192 | # Packages
193 | *.egg
194 | *.egg-info
195 | dist/
196 | build/
197 | eggs/
198 | parts/
199 | var/
200 | sdist/
201 | develop-eggs/
202 | .installed.cfg
203 |
204 | # Installer logs
205 | pip-log.txt
206 |
207 | # Unit test / coverage reports
208 | .coverage
209 | .tox
210 |
211 | #Translations
212 | *.mo
213 |
214 | #Mr Developer
215 | .mr.developer.cfg
216 |
217 | #InteliJIdea
218 | target/
219 | .idea/
220 | *.iml
--------------------------------------------------------------------------------
/src/test/java/com/cloudbees/jenkins/plugins/AdditionalIdentitiesIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package com.cloudbees.jenkins.plugins;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertNotNull;
5 |
6 | import hudson.model.User;
7 | import java.util.Collections;
8 | import java.util.HashMap;
9 | import java.util.Map;
10 | import org.junit.jupiter.api.BeforeEach;
11 | import org.junit.jupiter.api.DisplayName;
12 | import org.junit.jupiter.api.Test;
13 | import org.jvnet.hudson.test.JenkinsRule;
14 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
15 |
16 | @WithJenkins
17 | class AdditionalIdentitiesIntegrationTest {
18 |
19 | private JenkinsRule jenkins;
20 |
21 | @BeforeEach
22 | void setUp(JenkinsRule jenkins) {
23 | this.jenkins = jenkins;
24 | }
25 |
26 | @Test
27 | @DisplayName("Integration test for adding and resolving additional identities")
28 | void testAddingAndResolvingIdentities() throws Exception {
29 | // Given
30 | var userId = "test-user";
31 | var identityId = "github-user";
32 | var realm = "github";
33 |
34 | // Create user
35 | var user = User.getById(userId, true);
36 | assertNotNull(user);
37 |
38 | // Add additional identity to the user
39 | var identity = new AdditionalIdentity(identityId, realm);
40 | var additionalIdentities = new AdditionalIdentities(Collections.singletonList(identity));
41 | user.addProperty(additionalIdentities);
42 | user.save();
43 |
44 | // When we try to resolve the identity
45 | var resolver = new AdditionalIdentityResolver();
46 | var resolvedId = resolver.resolveCanonicalId(identityId, null);
47 |
48 | // Then the resolved id should match the user id
49 | assertEquals(userId, resolvedId);
50 | }
51 |
52 | @Test
53 | @DisplayName("Integration test for resolving identity with realm check")
54 | void testResolvingIdentityWithRealm() throws Exception {
55 | // Given
56 | var userId = "test-user-realm";
57 | var identityId = "github-user-realm";
58 | var realm = "github";
59 |
60 | // Create user
61 | var user = User.getById(userId, true);
62 | assertNotNull(user);
63 |
64 | // Add additional identity to the user
65 | var identity = new AdditionalIdentity(identityId, realm);
66 | var additionalIdentities = new AdditionalIdentities(Collections.singletonList(identity));
67 | user.addProperty(additionalIdentities);
68 | user.save();
69 |
70 | // When we try to resolve the identity with matching realm
71 | var resolver = new AdditionalIdentityResolver();
72 | Map context = new HashMap<>();
73 | context.put(User.CanonicalIdResolver.REALM, "jenkins-" + realm);
74 | var resolvedId = resolver.resolveCanonicalId(identityId, context);
75 |
76 | // Then the resolved id should match the user id
77 | assertEquals(userId, resolvedId);
78 | }
79 |
80 | @Test
81 | @DisplayName("Integration test for configuration via JCasC")
82 | void testConfigurationAsCode() throws Exception {
83 | // Given a user with additional identities
84 | var userId = "jcasc-user";
85 | var identityId = "jcasc-github-user";
86 | var realm = "github";
87 |
88 | // Create user
89 | var user = User.getById(userId, true);
90 | assertNotNull(user);
91 |
92 | // Add additional identity to the user
93 | var identity = new AdditionalIdentity(identityId, realm);
94 | var additionalIdentities = new AdditionalIdentities(Collections.singletonList(identity));
95 | user.addProperty(additionalIdentities);
96 | user.save();
97 |
98 | // When and Then
99 | var userFromJenkins = User.getById(userId, false);
100 | assertNotNull(userFromJenkins);
101 |
102 | var storedIdentities = userFromJenkins.getProperty(AdditionalIdentities.class);
103 | assertNotNull(storedIdentities);
104 | assertEquals(1, storedIdentities.getIdentities().size());
105 | assertEquals(identityId, storedIdentities.getIdentities().get(0).getId());
106 | assertEquals(realm, storedIdentities.getIdentities().get(0).getRealm());
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 | 4.0.0
27 |
28 | org.jenkins-ci.plugins
29 | plugin
30 | 5.28
31 |
32 |
33 |
34 | com.cloudbees.jenkins.plugins
35 | additional-identities-plugin
36 | ${revision}.${changelist}
37 | hpi
38 |
39 | Additional Identities Plugin
40 | https://github.com/jenkinsci/${project.artifactId}
41 |
42 |
43 |
44 | MIT
45 | https://opensource.org/license/mit
46 | repo
47 |
48 |
49 |
50 |
51 | scm:git:https://github.com/jenkinsci/${project.artifactId}.git
52 | scm:git:git@github.com:jenkinsci/${project.artifactId}.git
53 | ${scmTag}
54 | https://github.com/jenkinsci/${project.artifactId}
55 |
56 |
57 |
58 |
59 | 182
60 | 999999-SNAPSHOT
61 | 2.528
62 | ${jenkins.baseline}.3
63 | false
64 | false
65 | true
66 |
67 |
68 |
69 |
70 |
71 |
72 | io.jenkins.tools.bom
73 | bom-${jenkins.baseline}.x
74 | 5804.v80587a_38d937
75 | pom
76 | import
77 |
78 |
79 |
80 |
81 |
82 |
83 | io.jenkins
84 | configuration-as-code
85 | test
86 |
87 |
88 | io.jenkins.configuration-as-code
89 | test-harness
90 | test
91 |
92 |
93 | org.mockito
94 | mockito-core
95 | test
96 |
97 |
98 | org.mockito
99 | mockito-junit-jupiter
100 | test
101 |
102 |
103 |
104 |
105 |
106 | repo.jenkins-ci.org
107 | https://repo.jenkins-ci.org/public/
108 |
109 |
110 |
111 |
112 |
113 | repo.jenkins-ci.org
114 | https://repo.jenkins-ci.org/public/
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/src/test/java/com/cloudbees/jenkins/plugins/AdditionalIdentityResolverTest.java:
--------------------------------------------------------------------------------
1 | package com.cloudbees.jenkins.plugins;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertNull;
5 | import static org.mockito.Mockito.lenient;
6 | import static org.mockito.Mockito.mock;
7 | import static org.mockito.Mockito.when;
8 |
9 | import hudson.model.User;
10 | import java.util.Collections;
11 | import java.util.HashMap;
12 | import java.util.Map;
13 | import org.junit.jupiter.api.BeforeEach;
14 | import org.junit.jupiter.api.DisplayName;
15 | import org.junit.jupiter.api.Test;
16 | import org.junit.jupiter.api.extension.ExtendWith;
17 | import org.mockito.MockedStatic;
18 | import org.mockito.Mockito;
19 | import org.mockito.junit.jupiter.MockitoExtension;
20 |
21 | @ExtendWith(MockitoExtension.class)
22 | class AdditionalIdentityResolverTest {
23 |
24 | private AdditionalIdentityResolver resolver;
25 |
26 | @BeforeEach
27 | void setUp() {
28 | resolver = new AdditionalIdentityResolver();
29 | }
30 |
31 | @Test
32 | @DisplayName("Should return null when no users are found")
33 | void testNoUsersFound() {
34 | try (MockedStatic userMock = Mockito.mockStatic(User.class)) {
35 | // Given
36 | userMock.when(User::getAll).thenReturn(Collections.emptyList());
37 |
38 | // When
39 | var result = resolver.resolveCanonicalId("test-id", null);
40 |
41 | // Then
42 | assertNull(result);
43 | }
44 | }
45 |
46 | @Test
47 | @DisplayName("Should return null when user has no additional identities")
48 | void testUserWithNoAdditionalIdentities() {
49 | try (MockedStatic userMock = Mockito.mockStatic(User.class)) {
50 | // Given
51 | var user = mock(User.class);
52 | when(user.getProperty(AdditionalIdentities.class)).thenReturn(null);
53 | userMock.when(User::getAll).thenReturn(Collections.singletonList(user));
54 |
55 | // When
56 | var result = resolver.resolveCanonicalId("test-id", null);
57 |
58 | // Then
59 | assertNull(result);
60 | }
61 | }
62 |
63 | @Test
64 | @DisplayName("Should return user ID when identity matches without realm check")
65 | void testMatchingIdentity() {
66 | try (MockedStatic userMock = Mockito.mockStatic(User.class)) {
67 | // Given
68 | var userId = "user123";
69 | var identityId = "github-user";
70 |
71 | var user = mock(User.class);
72 | var identity = new AdditionalIdentity(identityId, null);
73 | var identities = new AdditionalIdentities(Collections.singletonList(identity));
74 |
75 | when(user.getProperty(AdditionalIdentities.class)).thenReturn(identities);
76 | when(user.getId()).thenReturn(userId);
77 | userMock.when(User::getAll).thenReturn(Collections.singletonList(user));
78 |
79 | // When
80 | var result = resolver.resolveCanonicalId(identityId, null);
81 |
82 | // Then
83 | assertEquals(userId, result);
84 | }
85 | }
86 |
87 | @Test
88 | @DisplayName("Should not match when realms don't match")
89 | void testNonMatchingRealm() {
90 | try (MockedStatic userMock = Mockito.mockStatic(User.class)) {
91 | // Given
92 | var userId = "user123";
93 | var identityId = "github-user";
94 | var identityRealm = "github";
95 | var contextRealm = "gitlab";
96 |
97 | var user = mock(User.class);
98 | var identity = new AdditionalIdentity(identityId, identityRealm);
99 | var identities = new AdditionalIdentities(Collections.singletonList(identity));
100 |
101 | // Use lenient() to avoid unnecessary stubbing exception
102 | lenient().when(user.getProperty(AdditionalIdentities.class)).thenReturn(identities);
103 | lenient().when(user.getId()).thenReturn(userId);
104 | userMock.when(User::getAll).thenReturn(Collections.singletonList(user));
105 |
106 | Map context = new HashMap<>();
107 | context.put(User.CanonicalIdResolver.REALM, contextRealm);
108 |
109 | // When
110 | var result = resolver.resolveCanonicalId(identityId, context);
111 |
112 | // Then
113 | assertNull(result);
114 | }
115 | }
116 |
117 | @Test
118 | @DisplayName("Should match when realms match")
119 | void testMatchingRealm() {
120 | try (MockedStatic userMock = Mockito.mockStatic(User.class)) {
121 | // Given
122 | var userId = "user123";
123 | var identityId = "github-user";
124 | var realm = "github";
125 |
126 | var user = mock(User.class);
127 | var identity = new AdditionalIdentity(identityId, realm);
128 | var identities = new AdditionalIdentities(Collections.singletonList(identity));
129 |
130 | when(user.getProperty(AdditionalIdentities.class)).thenReturn(identities);
131 | when(user.getId()).thenReturn(userId);
132 | userMock.when(User::getAll).thenReturn(Collections.singletonList(user));
133 |
134 | Map context = new HashMap<>();
135 | context.put(User.CanonicalIdResolver.REALM, "contains-" + realm + "-realm");
136 |
137 | // When
138 | var result = resolver.resolveCanonicalId(identityId, context);
139 |
140 | // Then
141 | assertEquals(userId, result);
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------