authorities;
705 | if (user != null) {
706 | authorities = authToken.getAuthorities();
707 | } else {
708 | authorities = List.of();
709 | }
710 | return new GithubOAuthUserDetails(username, authorities);
711 | }
712 |
713 | try {
714 | GithubOAuthUserDetails userDetails = authToken.getUserDetails(username);
715 | if (userDetails == null) {
716 | throw new UsernameNotFoundException("Unknown user: " + username);
717 | }
718 |
719 | // Check the username is not an homonym of an organization
720 | GHOrganization ghOrg = authToken.loadOrganization(username);
721 | if (ghOrg != null) {
722 | throw new UsernameNotFoundException("user(" + username + ") is also an organization");
723 | }
724 |
725 | return userDetails;
726 | } catch (IOException | Error e) {
727 | throw new AuthenticationServiceException("loadUserByUsername (username=" + username +")", e);
728 | }
729 | }
730 |
731 | /**
732 | * Compare an object against this instance for equivalence.
733 | * @param object An object to campare this instance to.
734 | * @return true if the objects are the same instance and configuration.
735 | */
736 | @Override
737 | public boolean equals(Object object){
738 | if(object instanceof GithubSecurityRealm) {
739 | GithubSecurityRealm obj = (GithubSecurityRealm) object;
740 | return this.getGithubWebUri().equals(obj.getGithubWebUri()) &&
741 | this.getGithubApiUri().equals(obj.getGithubApiUri()) &&
742 | this.getClientID().equals(obj.getClientID()) &&
743 | this.getClientSecret().equals(obj.getClientSecret()) &&
744 | this.getOauthScopes().equals(obj.getOauthScopes());
745 | } else {
746 | return false;
747 | }
748 | }
749 |
750 | @Override
751 | public int hashCode() {
752 | return new HashCodeBuilder()
753 | .append(this.getGithubWebUri())
754 | .append(this.getGithubApiUri())
755 | .append(this.getClientID())
756 | .append(this.getClientSecret())
757 | .append(this.getOauthScopes())
758 | .toHashCode();
759 | }
760 |
761 | /**
762 | *
763 | * @param groupName groupName to look up
764 | * @return groupDetails
765 | */
766 | @Override
767 | public GroupDetails loadGroupByGroupname2(String groupName, boolean fetchMembers)
768 | throws UsernameNotFoundException {
769 | GithubAuthenticationToken authToken = (GithubAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
770 |
771 | if(authToken == null)
772 | throw new UsernameNotFoundException("No known group: " + groupName);
773 |
774 | try {
775 | int idx = groupName.indexOf(GithubOAuthGroupDetails.ORG_TEAM_SEPARATOR);
776 | if (idx > -1 && groupName.length() > idx + 1) { // groupName = "GHOrganization*GHTeam"
777 | String orgName = groupName.substring(0, idx);
778 | String teamName = groupName.substring(idx + 1);
779 | LOGGER.config(String.format("Lookup for team %s in organization %s", teamName, orgName));
780 | GHTeam ghTeam = authToken.loadTeam(orgName, teamName);
781 | if (ghTeam == null) {
782 | throw new UsernameNotFoundException("Unknown GitHub team: " + teamName + " in organization "
783 | + orgName);
784 | }
785 | return new GithubOAuthGroupDetails(ghTeam);
786 | } else { // groupName = "GHOrganization"
787 | GHOrganization ghOrg = authToken.loadOrganization(groupName);
788 | if (ghOrg == null) {
789 | throw new UsernameNotFoundException("Unknown GitHub organization: " + groupName);
790 | }
791 | return new GithubOAuthGroupDetails(ghOrg);
792 | }
793 | } catch (Error e) {
794 | throw new AuthenticationServiceException("loadGroupByGroupname (groupname=" + groupName + ")", e);
795 | }
796 | }
797 |
798 | /**
799 | * Logger for debugging purposes.
800 | */
801 | private static final Logger LOGGER = Logger.getLogger(GithubSecurityRealm.class.getName());
802 |
803 | private static final String REFERER_ATTRIBUTE = GithubSecurityRealm.class.getName()+".referer";
804 | private static final String STATE_ATTRIBUTE = "state";
805 |
806 | private static final SecureRandom SECURE_RANDOM = new SecureRandom();
807 |
808 | }
809 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/JenkinsProxyAuthenticator.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins;
2 |
3 | import edu.umd.cs.findbugs.annotations.CheckForNull;
4 | import edu.umd.cs.findbugs.annotations.NonNull;
5 | import hudson.ProxyConfiguration;
6 | import hudson.util.Secret;
7 | import java.util.logging.Level;
8 | import java.util.logging.Logger;
9 | import okhttp3.Authenticator;
10 | import okhttp3.Challenge;
11 | import okhttp3.Credentials;
12 | import okhttp3.Request;
13 | import okhttp3.Response;
14 | import okhttp3.Route;
15 |
16 | public class JenkinsProxyAuthenticator implements Authenticator {
17 |
18 |
19 | private static final Logger LOGGER =
20 | Logger.getLogger(JenkinsProxyAuthenticator.class.getName());
21 |
22 | private final ProxyConfiguration proxy;
23 |
24 | public JenkinsProxyAuthenticator(ProxyConfiguration proxy) {
25 | this.proxy = proxy;
26 | }
27 |
28 | @CheckForNull
29 | @Override
30 | public Request authenticate(@CheckForNull Route route, @NonNull Response response) {
31 |
32 | if (response.request().header("Proxy-Authorization") != null) {
33 | return null; // Give up since we already tried to authenticate
34 | }
35 |
36 | if (response.challenges().isEmpty()) {
37 | // Proxy does not require authentication
38 | return null;
39 | }
40 |
41 | // Refuse pre-emptive challenge
42 | if (response.challenges().size() == 1) {
43 | Challenge challenge = response.challenges().get(0);
44 | if (challenge.scheme().equalsIgnoreCase("OkHttp-Preemptive")) {
45 | return null;
46 | }
47 | }
48 |
49 | for (Challenge challenge : response.challenges()) {
50 | if (challenge.scheme().equalsIgnoreCase("Basic")) {
51 | String username = proxy.getUserName();
52 | Secret password = proxy.getSecretPassword();
53 | if (username != null && password != null) {
54 | String credentials = Credentials.basic(username, password.getPlainText());
55 | return response.request()
56 | .newBuilder()
57 | .header("Proxy-Authorization", credentials)
58 | .build();
59 | } else {
60 | LOGGER.log(
61 | Level.WARNING,
62 | "Proxy requires Basic authentication but no username and password have been configured for the proxy");
63 | }
64 | break;
65 | }
66 | }
67 |
68 | LOGGER.log(
69 | Level.WARNING,
70 | "Proxy requires authentication, but does not support Basic authentication");
71 | return null;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | Authentication plugin using GitHub OAuth to provide authentication and authorization capabilities for GitHub and GitHub Enterprise.
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/GithubAuthorizationStrategy/config.jelly:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/GithubLogoutAction/index.jelly:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | You are now logged out of Jenkins. However, you have not been logged out of ${it.gitHubText}.
32 | Have a nice day!
33 |
34 |
35 | Whoa there...
36 | You are not logged out - don't run away!
37 | Whilst you should have been logged out, you are not actually logged out. Press the logout button to try logging out again.
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/GithubSecurityRealm/config.jelly:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/main/webapp/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/github-oauth-plugin/f5ddfba7349dac0eec130482ca98b6ab236f0c49/src/main/webapp/github.png
--------------------------------------------------------------------------------
/src/main/webapp/help/auth/admin-user-names-help.html:
--------------------------------------------------------------------------------
1 |
2 | Comma Separated list of user names that when authenticated should be given administrator rights.
3 |
--------------------------------------------------------------------------------
/src/main/webapp/help/auth/agent-user-name-help.html:
--------------------------------------------------------------------------------
1 |
2 | If you are using inbound Jenkins agents, this is the user that is used for authenticating agents. This user will receive rights to create, connect and configure agents.
3 |
--------------------------------------------------------------------------------
/src/main/webapp/help/auth/grant-create-job-to-authenticated-help.html:
--------------------------------------------------------------------------------
1 |
2 | If checked will grant the create job permission to all authenticated github users even those that aren't members of the declared organizations.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help/auth/grant-read-to-anonymous-help.html:
--------------------------------------------------------------------------------
1 |
2 | If checked will grant the READ permission to everyone that connects to the Jenkins instance. Anyone will be able to see all the jobs and related state but only authenticated users will be able to BUILD the jobs.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help/auth/grant-read-to-authenticated-help.html:
--------------------------------------------------------------------------------
1 |
2 | If checked will grant the read permission to all authenticated github users even those that aren't members of the declared organizations.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help/auth/grant-read-to-cctray-help.html:
--------------------------------------------------------------------------------
1 |
2 | Open a hole in security to allow unauthenticated access to URLs ending with /cc.xml.
3 | This URI provides
monitoring capability
4 | to a range of desktop clients.
5 |
6 | Enabling this option reveals limited information about your build to the whole world. Use with care.
7 |
--------------------------------------------------------------------------------
/src/main/webapp/help/auth/grant-read-to-github-webhook-help.html:
--------------------------------------------------------------------------------
1 |
2 | The
github-plugin has a web hook trigger that can be used to notify Jenkins when a push has occurred and that a build should also happen.
By enabling this permission you grant anonymous external READ access to the
/github-webhook URL so that the request can be received.
3 |
4 | The
github-plugin checks that a change has actually occurred so this should be safe to enable.
5 |
6 |
--------------------------------------------------------------------------------
/src/main/webapp/help/auth/grant-viewstatus-to-anonymous-help.html:
--------------------------------------------------------------------------------
1 |
2 | If checked will grant the ViewStatus permission to everyone that connects to the Jenkins instance. Anyone will be able to see the status of a job, but nothing else.
3 | This is useful especially for plugins like
Embeddable Build Status Plugin.
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help/auth/organization-names-help.html:
--------------------------------------------------------------------------------
1 |
2 | Comma Separated list of organization names that if a user is registered with will have job view and build rights.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help/auth/use-repository-permissions-help.html:
--------------------------------------------------------------------------------
1 |
2 | If checked will use github repository permissions to determine jenkins permissions for each project.
3 |
4 | - Public projects - all authenticated users can READ. Only collaborators can BUILD, EDIT, CONFIGURE, CANCEL or DELETE.
5 |
- Private projects, only collaborators can READ, BUILD, EDIT, CONFIGURE, CANCEL or DELETE
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/webapp/help/help-authorization-strategy.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Requires the
Github Authentication Plugin to be used as the authentication source.
4 |
5 | We use the OAuth token for each authenticated github user to interact with the Github API to determine the level of access each user should have.
6 |
7 |
8 |
9 | We grant READ and BUILD job permissions to an authenticated user if they are a member in at least one named organization.
10 |
11 | We also support defining a set of
Jenkins Admin users and whether or not any authenticated user can have READ access to the jobs.
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/webapp/help/help-security-realm.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Authentication requires a valid OAuth application to be registered with github.com.
4 |
5 | Using your github account you can create a new application registration using this link:
6 |
https://github.com/settings/applications/new
7 |
8 |
9 | You can use any name you want but bear in mind this is what github will show to the users when they authenticate and authorize this plugin to act on their behalf.
10 |
11 |
12 | The entry should look like this:
13 |
14 |
15 | Main URL | http://127.0.0.1:8080 |
16 | Callback URL | http://127.0.0.1:8080/securityRealm/finishLogin |
17 |
18 |
19 | With
127.0.0.1:8080 replaced with the public hostname and port of your jenkins instance. If the server is private it should be a url that the browser will know how to get to or the IP that the Jenkins instance is being accessed through.
20 |
21 |
22 | The
/securityRealm/finishLogin is required as this is where github will redirect the user for the second part of the autorization process.
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/main/webapp/help/realm/client-id-help.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help/realm/client-secret-help.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/main/webapp/help/realm/github-api-uri-help.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Normally this value stays as-is. If you are using GitHub Enterprise
4 | then you should enter the URI to the API root of your GitHub installation
5 | (e.g. https://github.company.com/api/v3).
6 |
7 |
8 | Notes:
9 |
10 |
11 | - The https:// / http:// part needs to be specified.
12 | - There should not be any trailing slash (/)
13 | - For GitHub Enterprise, the API URI is usually the same as the Web URI, with /api/v3 appended.
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/webapp/help/realm/github-web-uri-help.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Normally this value stays as-is. If you are using GitHub Enterprise
4 | then you should enter the URL to the web UI root of your GitHub installation
5 | (e.g. https://github.company.com).
6 |
7 |
8 | Notes:
9 |
10 |
11 | - The https:// / http:// part needs to be specified.
12 | - There should not be any trailing slash (/).
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main/webapp/help/realm/oauth-scopes-help.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/GithubAccessTokenPropertySEC797Test.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2017, 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 org.jenkinsci.plugins;
25 |
26 | import com.sun.net.httpserver.HttpExchange;
27 | import com.sun.net.httpserver.HttpHandler;
28 | import com.sun.net.httpserver.HttpServer;
29 | import edu.umd.cs.findbugs.annotations.CheckForNull;
30 | import hudson.model.UnprotectedRootAction;
31 | import hudson.util.HttpResponses;
32 | import jakarta.servlet.http.HttpSession;
33 | import net.sf.json.JSONArray;
34 | import net.sf.json.JSONObject;
35 | import org.apache.commons.lang.StringUtils;
36 | import org.eclipse.jetty.util.Fields;
37 | import org.eclipse.jetty.util.UrlEncoded;
38 | import org.htmlunit.Page;
39 | import org.htmlunit.WebRequest;
40 | import org.junit.jupiter.api.AfterEach;
41 | import org.junit.jupiter.api.BeforeEach;
42 | import org.junit.jupiter.api.Test;
43 | import org.jvnet.hudson.test.Issue;
44 | import org.jvnet.hudson.test.JenkinsRule;
45 | import org.jvnet.hudson.test.TestExtension;
46 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
47 | import org.kohsuke.stapler.HttpResponse;
48 | import org.kohsuke.stapler.StaplerRequest2;
49 |
50 | import java.io.IOException;
51 | import java.io.OutputStream;
52 | import java.net.HttpURLConnection;
53 | import java.net.InetAddress;
54 | import java.net.InetSocketAddress;
55 | import java.net.URI;
56 | import java.net.URL;
57 | import java.nio.charset.StandardCharsets;
58 | import java.util.ArrayList;
59 | import java.util.Collections;
60 | import java.util.HashMap;
61 | import java.util.List;
62 | import java.util.Map;
63 |
64 | import static org.junit.jupiter.api.Assertions.assertNotEquals;
65 | import static org.junit.jupiter.api.Assertions.assertNotNull;
66 |
67 | //TODO merge with GithubAccessTokenPropertyTest after security release, just meant to ease the security merge
68 | // or with GithubSecurityRealmTest, but will require more refactor to move out the mock server
69 | @WithJenkins
70 | class GithubAccessTokenPropertySEC797Test {
71 |
72 | private JenkinsRule j;
73 | private JenkinsRule.WebClient wc;
74 |
75 | private HttpServer server;
76 | private URI serverUri;
77 | private MockGithubServlet servlet;
78 |
79 | private void setupMockGithubServer() throws Exception {
80 | server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
81 | servlet = new MockGithubServlet(j);
82 | server.createContext("/", servlet);
83 | server.start();
84 |
85 | InetSocketAddress address = server.getAddress();
86 | serverUri = new URI(String.format("http://%s:%d/", address.getHostString(), address.getPort()));
87 | servlet.setServerUrl(serverUri);
88 | }
89 |
90 | /**
91 | * Based on documentation found at
92 | * https://developer.github.com/v3/users/
93 | * https://developer.github.com/v3/orgs/
94 | * https://developer.github.com/v3/orgs/teams/
95 | */
96 | private static class MockGithubServlet implements HttpHandler {
97 | private String currentLogin;
98 | private List organizations;
99 | private List teams;
100 |
101 | private final JenkinsRule jenkinsRule;
102 | private URI serverUri;
103 |
104 | public MockGithubServlet(JenkinsRule jenkinsRule) {
105 | this.jenkinsRule = jenkinsRule;
106 | }
107 |
108 | public void setServerUrl(URI serverUri) {
109 | this.serverUri = serverUri;
110 | }
111 |
112 | @Override
113 | public void handle(HttpExchange he) throws IOException {
114 | switch (he.getRequestURI().getPath()) {
115 | case "/user":
116 | this.onUser(he);
117 | break;
118 | case "/users/_specific_login_":
119 | this.onUser(he);
120 | break;
121 | case "/user/orgs":
122 | this.onUserOrgs(he);
123 | break;
124 | case "/user/teams":
125 | this.onUserTeams(he);
126 | break;
127 | case "/orgs/org-a":
128 | this.onOrgs(he, "org-a");
129 | break;
130 | case "/orgs/org-a/teams":
131 | this.onOrgsTeam(he, "org-a");
132 | break;
133 | case "/orgs/org-a/members/alice":
134 | this.onOrgsMember(he, "org-a", "alice");
135 | break;
136 | case "/teams/7/members/alice":
137 | this.onTeamMember(he, "team-b", "alice");
138 | break;
139 | case "/orgs/org-c":
140 | this.onOrgs(he, "org-c");
141 | break;
142 | case "/orgs/org-c/teams":
143 | this.onOrgsTeam(he, "org-c");
144 | break;
145 | case "/orgs/org-c/members/bob":
146 | this.onOrgsMember(he, "org-c", "bob");
147 | break;
148 | case "/teams/7/members/bob":
149 | this.onTeamMember(he, "team-d", "bob");
150 | break;
151 | case "/login/oauth/authorize":
152 | this.onLoginOAuthAuthorize(he);
153 | break;
154 | case "/login/oauth/access_token":
155 | this.onLoginOAuthAccessToken(he);
156 | break;
157 | default:
158 | throw new RuntimeException("Url not mapped yet: " + he.getRequestURI().getPath());
159 | }
160 | he.close();
161 | }
162 |
163 | private void onUser(HttpExchange he) throws IOException {
164 | sendResponse(he, JSONObject.fromObject(
165 | new HashMap() {{
166 | put("login", currentLogin);
167 | put("name", currentLogin + "_name");
168 | // to avoid triggering a second call, due to GithubSecurityRealm:382
169 | put("created_at", "2008-01-14T04:33:35Z");
170 | put("url", serverUri + "/users/_specific_login_");
171 | }}
172 | ).toString());
173 | }
174 |
175 | private void onUserOrgs(HttpExchange he) throws IOException {
176 | List