Best way of building oauth into Jenkins Plugin

74 views
Skip to first unread message

Thomas Einwaller

unread,
Dec 23, 2015, 4:56:24 AM12/23/15
to Jenkins Users

I am working on a Jenkins plugin that uses the new Bitbucket Build Status API. The best way to access the API is using oauth.

What is the best way of building oauth into my Jenkins plugin? Should I use a oauth Java library or is there another Jenkins plugin I can depend my plugin on?

How would the process of "connecting" Jenkins and my plugin to the Bitbucket account (granting access and storing the tokens)?


Stephen Connolly

unread,
Dec 26, 2015, 3:11:03 PM12/26/15
to jenkins...@googlegroups.com
I've implemented a Credential type that stores the OAuth details and uses a form on a new window to present the OAuth flow and then populate back the token, e.g. 

in the credentials.jelly:

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
  <j:set var="needAuth" value="${instance==null || instance.userId==null || instance.userId.length() == 0}"/>
  <j:if test="${!needAuth}">
    <f:entry title="${%Account}" >
      <div style="display:none">
        <f:password field="oAuth2RefreshToken"/>
      </div>
      <f:textbox field="userId" disabled="true"/>
    </f:entry>
  </j:if>
  <j:if test="${needAuth}">
    <j:set var="authId" value="${descriptor.newAuthId}"/>
    <f:entry title="${%Account}">
      <div id="${authId}">

      <input type="button" value="${%Authenticate}..." onclick="window.open('${rootUrl}/descriptor/com.cloudbees.jenkins.plugins.gaecredentials.GoogleAppEngineCredentialsImpl/authenticate?authId=${authId}', 'auth').opener=window; return false;" class="submit-button" />

      </div>
    </f:entry>
  </j:if>
</j:jelly>

Then in the callback's index.jelly
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" >
<l:layout>
  <l:main-panel>
  <j:set var="instance" value="${it}"/>
      <div style="display:none">
        <div id="content">
          <div style="display:none">
            <f:password field="oAuth2RefreshToken"/>
          </div>
          <f:textbox field="userId" disabled="true"/>
        </div>
      </div>
      <script>
        try {
          var id="${it.authId}";
          var newDiv = document.getElementById('content');
          var oldDiv = window.opener.document.getElementById(id);
          oldDiv.innerHTML = newDiv.innerHTML;
        } catch (e) {
          // ignore
        }
        window.close();
      </script>
  </l:main-panel>
</l:layout>
</j:jelly>
And the callback class looks something like:

    public static class Callback {

        private final Secret oAuth2RefreshToken;

        private final String userId;

        private final String authId;

        public Callback(GoogleCredential accessTokenResponse, Userinfo userinfo, String authId) {
            this.userId = userinfo.getEmail();
            this.oAuth2RefreshToken = Secret.fromString(accessTokenResponse.getRefreshToken());
            this.authId = authId;
        }

        public Secret getOAuth2RefreshToken() {
            return oAuth2RefreshToken;
        }

        public String getUserId() {
            return userId;
        }

        public String getAuthId() {
            return authId;
        }
    }

Which gets instantiated using this method (which is bound to the callback endpoint)

        public void doIndex(StaplerRequest request, StaplerResponse response, @QueryParameter String state,
                            @QueryParameter String code) throws IOException, ServletException {
            // parse the code
            NetHttpTransport transport = new NetHttpTransport();
            JacksonFactory jsonFactory = new JacksonFactory();
            TokenResponse tokenResponse = null;
            try {
                tokenResponse =
                        new GoogleAuthorizationCodeTokenRequest(
                                transport, jsonFactory, CLIENT_ID, CLIENT_SECRET, code,
                                getCallbackUrl()
                        ).execute();
            } catch (HttpResponseException e) {
            }

            // look up the email address

            GoogleCredential credential =
                    new GoogleCredential.Builder()
                            .setTransport(transport)
                            .setJsonFactory(jsonFactory)
                            .setClientSecrets(CLIENT_ID, CLIENT_SECRET)
                            .build()
                            .setFromTokenResponse(tokenResponse);

            Oauth2 oauth2 = new Oauth2.Builder(credential.getTransport(), credential.getJsonFactory(), null)
                    .setHttpRequestInitializer(credential)
                    .setJsonHttpRequestInitializer(new JsonHttpRequestInitializer() {
                        public void initialize(JsonHttpRequest request) throws IOException {
                            if (request instanceof Oauth2Request) {
                                Oauth2Request oauth2Request = (Oauth2Request) request;
                                oauth2Request.setPrettyPrint(true);
                                oauth2Request.setKey(CLIENT_ID);
                            }
                        }
                    })
                    .build();

            Userinfo userinfo = oauth2.userinfo().get().execute();

            // now sort out passing back the details

            response.forward(new Callback(credential, userinfo, request.getParameter("state")), "index", request);
        }


So what happens is that if the credential has just been created there is an Authenticate button... clicking the button will open a new window (tab) for a page from the local domain (so that we can interact via scripts). That URL does an immediate redirect to the OAuth end-point passing back the callback URL. Once OAuth completes it comes back to the callback, which captures the authorization and renders a page that uses javascript to close itself after injecting back the reference into the page that opened the tab in the first place.

There may be other ways, but that was the easiest to keep working








--
You received this message because you are subscribed to the Google Groups "Jenkins Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jenkinsci-use...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jenkinsci-users/d63d3781-48ae-4704-8365-6258d44592a0%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages