├── .github ├── dependabot.yml └── workflows │ ├── cd.yaml │ └── jenkins-security-scan.yml ├── .gitignore ├── .mvn ├── extensions.xml └── maven.config ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Jenkinsfile ├── Jenkinsfile.google ├── LICENSE ├── README.md ├── checkstyle.xml ├── checkstyleJavaHeader ├── docs ├── home.md ├── images │ ├── dropdown.jpg │ ├── metadata.jpg │ └── private-key.jpg └── source_build_installation.md ├── jenkins ├── maven-pod.yaml └── saveAndCompress.sh ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── google │ │ └── jenkins │ │ └── plugins │ │ ├── credentials │ │ └── oauth │ │ │ ├── GoogleOAuth2Credentials.java │ │ │ ├── GoogleOAuth2ScopeRequirement.java │ │ │ ├── GoogleOAuth2ScopeSpecification.java │ │ │ ├── GoogleRobotCredentials.java │ │ │ ├── GoogleRobotCredentialsModule.java │ │ │ ├── GoogleRobotMetadataCredentials.java │ │ │ ├── GoogleRobotMetadataCredentialsModule.java │ │ │ ├── GoogleRobotNameProvider.java │ │ │ ├── GoogleRobotPrivateKeyCredentials.java │ │ │ ├── JsonKey.java │ │ │ ├── JsonServiceAccountConfig.java │ │ │ ├── KeyUtils.java │ │ │ ├── LegacyJsonKey.java │ │ │ ├── P12ServiceAccountConfig.java │ │ │ ├── RemotableGoogleCredentials.java │ │ │ ├── ServiceAccountConfig.java │ │ │ └── package-info.java │ │ └── util │ │ ├── ConflictException.java │ │ ├── Executor.java │ │ ├── ExecutorException.java │ │ ├── ForbiddenException.java │ │ ├── MaxRetryExceededException.java │ │ ├── MetadataReader.java │ │ ├── MockExecutor.java │ │ ├── NameValuePair.java │ │ ├── NotFoundException.java │ │ ├── RequestCallable.java │ │ └── Resolve.java └── resources │ ├── com │ └── google │ │ └── jenkins │ │ └── plugins │ │ ├── credentials │ │ └── oauth │ │ │ ├── GoogleRobotMetadataCredentials │ │ │ ├── config.jelly │ │ │ └── help-projectId.html │ │ │ ├── GoogleRobotPrivateKeyCredentials │ │ │ ├── config.jelly │ │ │ ├── help-credentials.html │ │ │ └── help-projectId.html │ │ │ ├── JsonServiceAccountConfig │ │ │ ├── config.jelly │ │ │ └── help-jsonKeyFileUpload.html │ │ │ ├── Messages.properties │ │ │ └── P12ServiceAccountConfig │ │ │ ├── config.jelly │ │ │ ├── help-emailAddress.html │ │ │ └── help-p12KeyFileUpload.html │ │ └── util │ │ └── Messages.properties │ ├── index.jelly │ └── lib │ └── auth │ ├── blockWrapper.jelly │ ├── credentials.jelly │ └── taglib └── test ├── java └── com │ └── google │ └── jenkins │ ├── GoogleOAuthPluginTestSuite.java │ └── plugins │ ├── CredentialsOAuthTestSuite.java │ ├── UtilTestSuite.java │ ├── credentials │ └── oauth │ │ ├── ConfigurationAsCodeTest.java │ │ ├── GoogleOAuth2ScopeSpecificationTest.java │ │ ├── GoogleRobotCredentialsTest.java │ │ ├── GoogleRobotMetadataCredentialsTest.java │ │ ├── GoogleRobotPrivateKeyCredentialsTest.java │ │ ├── JsonServiceAccountConfigTest.java │ │ ├── JsonServiceAccountConfigTestUtil.java │ │ ├── LegacyJsonServiceAccountConfigUtil.java │ │ ├── P12ServiceAccountConfigTest.java │ │ ├── P12ServiceAccountConfigTestUtil.java │ │ ├── RemotableGoogleCredentialsTest.java │ │ ├── SerializationUtil.java │ │ ├── TestGoogleOAuth2DomainRequirement.java │ │ └── TestRobotBuilder.java │ └── util │ ├── ExecutorTest.java │ ├── MetadataReaderTest.java │ ├── MockExecutorTest.java │ ├── NameValuePairTest.java │ └── ResolveTest.java └── resources └── com └── google └── jenkins └── plugins └── credentials └── oauth ├── GoogleRobotCredentialsTest └── testMigration │ └── credentials.xml ├── json-service-account-config.yml ├── p12-service-account-config.yml ├── sample-privatekey.p12 └── test-key.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: '/' 5 | schedule: 6 | interval: daily 7 | 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.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@v1 17 | secrets: 18 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 19 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 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@v2 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Maven 2 | target 3 | work 4 | 5 | # IntelliJ IDEA 6 | .idea/ 7 | *.iml 8 | 9 | # Jenkins 10 | work/ 11 | 12 | # Emacs 13 | *~ 14 | 15 | # eclipse 16 | .classpath 17 | .project 18 | .settings/ -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.7 6 | 7 | 8 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Dchangelist.format=%d.v%s 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | script: mvn clean test 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 16 | # Changelog 17 | All notable changes to this project will be documented in this file. 18 | 19 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 20 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 21 | 22 | ## [Unresolved] 23 | 24 | ### Security 25 | 26 | ### Added 27 | 28 | ### Changed 29 | 30 | ### Removed 31 | 32 | ### Fixed 33 | 34 | ## [1.0.0] 2019-10-29 35 | 36 | ### Changed 37 | - com.google.guava:guava version changed: 14.0.1 to 20.0 38 | - com.google.http-client:google-http-client version changed: 1.24.1 to 1.21.0 39 | - com.google.http-client:google-http-client-jackson2 version changed: 1.24.1 to 1.25.0 40 | - com.google.api-client:google-api-client version changed: 1.24.1 to 1.25.0 41 | - com.google.apis:google-api-services-oauth2 version changed: v2-rev140-1.24.1 to v2-rev151-1.25.0 42 | 43 | ## [0.10] 2019-10-09 44 | 45 | ### Security 46 | - SECURITY-1583: Added permissions checking for loading legacy key files when upgrading versions. 47 | 48 | ### Added 49 | - Onboarding project into team CI server. 50 | 51 | ## [0.9] 2019-09-03 52 | 53 | ### Added 54 | - Test suites for different kinds of tests 55 | - Release completion goal to format and tidy pom file after release and before 56 | creating the final git commit. 57 | 58 | ### Changed 59 | - Changed to use fixed link for GCP Slack invite. 60 | - Plugin parent org.jenkinsci.plugins:plugin version changed: 3.36 to 3.43 61 | - Jenkins core requirement changed: 2.60.3 to 2.138.4 62 | - io.jenkins:configuration-as-code version changed: 1.9 to 1.19 63 | - org.jenkins-ci.plugins:credentials version changed: 2.1.16 to 2.2.0 64 | - com.google.http-client:google-http-client-jackson replaced with 65 | com.google.http-client:google-http-client-jackson2 66 | 67 | ### Removed 68 | - Test dependencies previously required for testing configuration as code 69 | compatibility: 70 | - io.jenkins.configuration-as-code:configuration-as-code-support 71 | - org.jenkins-ci.plugins:plain-credentials 72 | - org.jenkins-ci.plugins:ssh-credentials 73 | 74 | ### Fixed 75 | - Javadoc links to classes. 76 | 77 | ## [0.8] 2019-04-24 78 | 79 | ### Security 80 | 81 | ### Added 82 | - License headers that were missing. 83 | - xml-format-maven plugin for formatting pom.xml. 84 | - tidy-maven plugin for maintaining a consistent structure in pom.xml. 85 | - Support for Jenkins Configuration as Code plugin. Use the base64 encoded 86 | secret directly in the configuration. `filename` is optional but recommended 87 | if you plan to also use the Jenkins UI. 88 | - Test-only dependencies on the configuration-as-code, plain credentials 89 | and ssh-credentials plugins. 90 | - fmt-maven-plugin for automatically formatting java files when built. 91 | 92 | ### Changed 93 | - Use SecretBytes to store credentials, rather than files. 94 | - When uploaded through the UI, the file name serves as an identifier for the 95 | key when choosing whether to upload a new key. The file name will be as 96 | uploaded rather than a new randomly generated file name 97 | - Updated base plugin version to 3.36 98 | - Updated Java version to 8 99 | - Updated minimum Jenkins version to 2.60.3 100 | - Updated credentials plugin to 2.1.16 101 | - Updated maven-javadoc-plugin to 2.10.4 102 | 103 | ### Removed 104 | - Issue #49: find-bugs plugin (spot-bugs now inherited from parent plugin pom) 105 | 106 | ### Fixed 107 | - Various issues related to the use of manually generated files 108 | - Not able to load files after system restart, leading to NPEs in some 109 | - Not able to update permissions on created files, so fail to have a usable key. 110 | 111 | ## [0.7] - 2019-02-06 112 | 113 | ### Security 114 | 115 | ### Added 116 | 117 | ### Changed 118 | - Upgraded google.api.version to 1.24.1 119 | - Updated joda-time to 2.9.5 120 | - Added git ignore for intellij and emacs 121 | - Added travis file 122 | 123 | ### Removed 124 | 125 | ### Fixed 126 | 127 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 16 | # How to contribute 17 | 18 | We'd love to accept your patches and contributions to this project. There are 19 | just a few small guidelines you need to follow. 20 | 21 | ## Branching Model 22 | 23 | This repository utilizes the [Git Flow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) branch model. All pull requests should be made against the develop branch. 24 | 25 | ## Code reviews 26 | 27 | All submissions, including submissions by project members, require review. We 28 | use GitHub pull requests for this purpose. Consult 29 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 30 | information on using pull requests. 31 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | buildPlugin(useContainerAgent: true, configurations: [ 2 | [platform: 'linux', jdk: 21], 3 | [platform: 'windows', jdk: 17], 4 | ]) 5 | -------------------------------------------------------------------------------- /Jenkinsfile.google: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | pipeline { 17 | agent { 18 | kubernetes { 19 | cloud 'kubernetes' 20 | label 'maven-pod' 21 | yamlFile 'jenkins/maven-pod.yaml' 22 | } 23 | } 24 | 25 | environment { 26 | GOOGLE_PROJECT_ID = "${OAUTH_IT_PROJECT_ID}" 27 | BUILD_ARTIFACTS_BUCKET = "${OAUTH_IT_BUCKET}" 28 | CLEAN_BRANCH_NAME = "${BRANCH_NAME}".replaceAll("[/&;<>|\\]]", "_") 29 | BUILD_ARTIFACTS = "build-${CLEAN_BRANCH_NAME}-${BUILD_ID}.tar.gz" 30 | } 31 | 32 | stages { 33 | stage("Build and test") { 34 | steps { 35 | container('maven') { 36 | withCredentials([[$class: 'StringBinding', credentialsId: env.OAUTH_IT_CRED_ID, variable: 'GOOGLE_CREDENTIALS']]) { 37 | catchError { 38 | // build 39 | sh "mvn clean package -ntp" 40 | 41 | // run tests 42 | sh "mvn verify -ntp" 43 | } 44 | 45 | sh "jenkins/saveAndCompress.sh" 46 | step([$class: 'ClassicUploadStep', credentialsId: env.OAUTH_BUCKET_CRED_ID, bucket: "gs://${BUILD_ARTIFACTS_BUCKET}", pattern: env.BUILD_ARTIFACTS]) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 16 | Jenkins Google OAuth Credentials Plugin 17 | ===================== 18 | The Google OAuth plugin provides a Google-specific implementation of the OAuth Credentials interfaces. 19 | 20 | ## Documentation 21 | Please see [Google OAuth Plugin](docs/home.md) for complete documentation. 22 | 23 | ## Installation 24 | 1. Go to **Manage Jenkins** then **Manage Plugins**. 25 | 1. (Optional) Make sure the plugin manager has updated data by clicking the **Check now** button. 26 | 1. In the Plugin Manager, click the **Available** tab and look for the "Google OAuth Credentials". 27 | 1. Check the box under the **Install** column and click the **Install without restart** button. 28 | 1. If the plugin does not appear under **Available**, make sure it appears under **Installed** and is enabled. 29 | 30 | ## Plugin Source Build Installation 31 | See [Plugin Source Build Installation](docs/source_build_installation.md) to build and install from source. 32 | 33 | ## Feature requests and bug reports 34 | Please file feature requests and bug reports as [GitHub Issues](https://github.com/jenkinsci/google-oauth-plugin/issues). 35 | 36 | ## Community 37 | 38 | The GCP Jenkins community uses the **#gcp-jenkins** slack channel on 39 | [https://googlecloud-community.slack.com](https://googlecloud-community.slack.com) 40 | to ask questions and share feedback. Invitation link available 41 | here: [gcp-slack](https://cloud.google.com/community#home-support). 42 | 43 | ## License 44 | See [LICENSE](LICENSE) 45 | 46 | ## Contributing 47 | See [CONTRIBUTING.md](CONTRIBUTING.md) 48 | -------------------------------------------------------------------------------- /checkstyleJavaHeader: -------------------------------------------------------------------------------- 1 | ^/\*$ 2 | ^ \* Copyright 20\d\d Google LLC*$ 3 | ^ \*$ 4 | ^ \* Licensed under the Apache License, Version 2\.0 \(the \"License\"\);$ 5 | ^ \* you may not use this file except in compliance with the License\.$ 6 | ^ \* You may obtain a copy of the License at$ 7 | ^ \*$ 8 | ^ \* https://www\.apache\.org/licenses/LICENSE-2\.0$ 9 | ^ \*$ 10 | ^ \* Unless required by applicable law or agreed to in writing, software$ 11 | ^ \* distributed under the License is distributed on an \"AS IS\" BASIS,$ 12 | ^ \* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.$ 13 | ^ \* See the License for the specific language governing permissions and$ 14 | ^ \* limitations under the License\.$ 15 | ^ \*/ 16 | -------------------------------------------------------------------------------- /docs/home.md: -------------------------------------------------------------------------------- 1 | 14 | # Google OAuth Plugin Documentation 15 | 16 | This plugin implements the OAuth Credentials interfaces for surfacing [Google Service Accounts](https://cloud.google.com/iam/docs/understanding-service-accounts) to Jenkins. This plugin allows for the registration of Google Service Account Credentials with the Jenkins controller, which can be used to interact with Google APIs. 17 | 18 | ## Google OAuth Credentials 19 | 20 | This plugin surfaces two Credential types for generating OAuth credentials for a Google service account: 21 | 22 | 1. [Google Service Account from private key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) use this option with the private key for your Google service account. See our embedded help section for details on how/where this is obtained, or here. 23 | * You can create service accounts via the Google Developers Console, under APIs & auth → Credentials → Create new Client ID → Service account 24 | * The downloaded P12 key and the service account email address can then be used when adding the credential to Jenkins; or if you created a new JSON key, you can use that instead 25 | 26 | 1. [Google Service Account from metadata](https://cloud.google.com/compute/docs/storing-retrieving-metadata) use this option for obtaining the service account information from Google Compute Engine metadata. This option is only available when the Jenkins controller is run on a Google Compute Engine VM. 27 | 28 | ![drop-down image](images/dropdown.jpg) 29 | 30 | ## Private Key Credentials 31 | 32 | Credentials generated from a private key provide uninhibited access to your project as the robot, so we also surface the necessary Domain specification functionality to restrict the scopes for which such a credential may be used by plugins. 33 | 34 | For example, if you only want this service account to be used for “Google Drive”, but there are plugins that might use “Google Calendar”, we want this credential to show up as an option for the former, but not the latter. 35 | 36 | An example of a private key restricted for use with [Google Cloud Storage](https://github.com/jenkinsci/google-storage-plugin). 37 | 38 | ![private-key](images/private-key.jpg) 39 | 40 | **NOTE**: As new plugins are installed, which bring in new OAuth scopes, this domain restriction allows those new scopes to be opt-in vs. opt-out. 41 | 42 | ## Metadata Credentials 43 | 44 | Credentials obtained via [Google Compute Engine metadata](https://cloud.google.com/compute/docs/) are inherently limited to the scopes to which you bound the VM when it was provisioned. You will see this set of scopes listed alongside the credential. 45 | 46 | ![metadata](images/metadata.jpg) 47 | 48 | For an example of how to consume these credentials, please see our [Google Cloud Storage Plugin](https://github.com/jenkinsci/google-storage-plugin). 49 | 50 | -------------------------------------------------------------------------------- /docs/images/dropdown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/google-oauth-plugin/31633a5e08313b7871be6e4622b5632699628e98/docs/images/dropdown.jpg -------------------------------------------------------------------------------- /docs/images/metadata.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/google-oauth-plugin/31633a5e08313b7871be6e4622b5632699628e98/docs/images/metadata.jpg -------------------------------------------------------------------------------- /docs/images/private-key.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/google-oauth-plugin/31633a5e08313b7871be6e4622b5632699628e98/docs/images/private-key.jpg -------------------------------------------------------------------------------- /docs/source_build_installation.md: -------------------------------------------------------------------------------- 1 | 14 | # Plugin Source Build Installation 15 | 16 | 1. Clone the plugin and enter the directory: 17 | ```bash 18 | git clone git@github.com:jenkinsci/google-oauth-plugin.git 19 | cd google-oauth-plugin 20 | ``` 21 | 1. Checkout the branch that you would like to build from: 22 | ```bash 23 | git checkout 24 | ``` 25 | 1. Build the plugin into a .hpi plugin file: 26 | ```bash 27 | mvn hpi:hpi 28 | ``` 29 | 1. Go to **Manage Jenkins** then **Manage Plugins**. 30 | 1. In the Plugin Manager, click the **Advanced** tab and then **Choose File** under the **Upload Plugin** section. 31 | 1. Choose the Jenkins plugin file built in Step 3. 32 | 1. Click the **Upload** button. 33 | -------------------------------------------------------------------------------- /jenkins/maven-pod.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Google Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: v1 16 | kind: Pod 17 | spec: 18 | containers: 19 | - name: maven 20 | image: maven:3.6.2-jdk-8 21 | command: ['cat'] 22 | tty: true 23 | -------------------------------------------------------------------------------- /jenkins/saveAndCompress.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2019 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | pwd 16 | mkdir result 17 | 18 | # The files will only exist if maven reaches the corresponding phase of the build 19 | function cpe() { 20 | if [[ -e target/$1 ]]; then 21 | cp -rv target/$1 result 22 | else 23 | echo "target/$1 not copied, check the completed build phases." 24 | fi 25 | } 26 | 27 | # Copy over the important artifacts 28 | cpe google-oauth-plugin.hpi 29 | cpe failsafe-reports 30 | cpe surefire-reports 31 | 32 | # Compress the artifacts for upload 33 | tar -zcvf ${BUILD_ARTIFACTS} result 34 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/GoogleOAuth2Credentials.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.google.api.client.auth.oauth2.Credential; 19 | import java.security.GeneralSecurityException; 20 | 21 | /** 22 | * Google-specific username / access token combination. 23 | * 24 | *

Implementations surface an API for obtaining the Google-standard {@link Credential} object for 25 | * interacting with OAuth2 APIs. 26 | */ 27 | public interface GoogleOAuth2Credentials extends StandardUsernameOAuth2Credentials { 28 | /** 29 | * Fetches a Credential for the set of OAuth 2.0 scopes required. 30 | * 31 | * @param requirement The set of required OAuth 2.0 scopes 32 | * @return The Credential authorizing usage of the API scopes 33 | * @throws GeneralSecurityException when the authentication fails 34 | */ 35 | Credential getGoogleCredential(GoogleOAuth2ScopeRequirement requirement) throws GeneralSecurityException; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/GoogleOAuth2ScopeRequirement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | /** Used to type filter Google-specific {@link OAuth2ScopeRequirement}s. */ 19 | public abstract class GoogleOAuth2ScopeRequirement extends OAuth2ScopeRequirement {} 20 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/GoogleOAuth2ScopeSpecification.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import hudson.Extension; 19 | import java.util.Collection; 20 | import org.kohsuke.stapler.DataBoundConstructor; 21 | 22 | /** 23 | * A Google-specific implementation of the {@link OAuth2ScopeSpecification} that limits its 24 | * application to Google-specific {@link OAuth2ScopeRequirement} 25 | */ 26 | public class GoogleOAuth2ScopeSpecification extends OAuth2ScopeSpecification { 27 | @DataBoundConstructor 28 | public GoogleOAuth2ScopeSpecification(Collection specifiedScopes) { 29 | super(specifiedScopes); 30 | } 31 | 32 | /** 33 | * Denoted this class is a {@code DomainSpecification} plugin, in particular for {@link 34 | * OAuth2ScopeSpecification} 35 | */ 36 | @Extension 37 | public static class DescriptorImpl extends OAuth2ScopeSpecification.Descriptor { 38 | public DescriptorImpl() { 39 | super(GoogleOAuth2ScopeRequirement.class); 40 | } 41 | 42 | /** {@inheritDoc} */ 43 | @Override 44 | public String getDisplayName() { 45 | return Messages.GoogleOAuth2ScopeSpecification_DisplayName(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/GoogleRobotCredentialsModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.google.api.client.http.HttpTransport; 19 | import com.google.api.client.http.javanet.NetHttpTransport; 20 | import com.google.api.client.json.JsonFactory; 21 | import com.google.api.client.json.jackson2.JacksonFactory; 22 | import java.io.Serializable; 23 | 24 | /** 25 | * An abstraction interface for instantiating the dependencies of the {@link 26 | * GoogleRobotCredentials}. 27 | */ 28 | public class GoogleRobotCredentialsModule implements Serializable { 29 | /** The HttpTransport to use for credential related requests. */ 30 | public HttpTransport getHttpTransport() { 31 | return new NetHttpTransport(); 32 | } 33 | 34 | /** The HttpTransport to use for credential related requests. */ 35 | public JsonFactory getJsonFactory() { 36 | return new JacksonFactory(); 37 | } 38 | 39 | /** For {@link Serializable} */ 40 | private static final long serialVersionUID = 1L; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/GoogleRobotMetadataCredentialsModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.google.jenkins.plugins.util.MetadataReader; 19 | 20 | /** 21 | * An abstraction interface for instantiating the dependencies of the {@link 22 | * GoogleRobotMetadataCredentials}. 23 | */ 24 | public class GoogleRobotMetadataCredentialsModule extends GoogleRobotCredentialsModule { 25 | /** Retrieve a MetadataReader for accessing stuff encoded in the instance metadata. */ 26 | public MetadataReader getMetadataReader() { 27 | return new MetadataReader.Default(); 28 | } 29 | 30 | /** For {@link java.io.Serializable} */ 31 | private static final long serialVersionUID = 1L; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/GoogleRobotNameProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.cloudbees.plugins.credentials.CredentialsNameProvider; 19 | 20 | /** Retrieve a user-friendly name to be used when listing the credential for use by plugins. */ 21 | public class GoogleRobotNameProvider extends CredentialsNameProvider { 22 | /** {@inheritDoc} */ 23 | @Override 24 | public String getName(GoogleRobotCredentials credentials) { 25 | return credentials.getProjectId(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/JsonKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.google.api.client.json.GenericJson; 19 | import com.google.api.client.json.JsonFactory; 20 | import com.google.api.client.util.Key; 21 | import com.google.common.base.Charsets; 22 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 23 | import hudson.util.Secret; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.io.InputStreamReader; 27 | import org.apache.commons.io.IOUtils; 28 | 29 | /** 30 | * The Google Developer Console provides private 31 | * keys for service accounts in two different ways. one of them is a .json file that can be 32 | * downloaded from the Google Developer Console. 33 | * 34 | *

The structure of this json file is: 35 | * { 36 | * "private_key":"-----BEGIN PRIVATE KEY-----\n 37 | * ... 38 | * \n-----END PRIVATE KEY-----\n", 39 | * "client_email":"...@developer.gserviceaccount.com", 40 | * ... 41 | * } 42 | * 43 | */ 44 | @SuppressFBWarnings("EQ_DOESNT_OVERRIDE_EQUALS") 45 | public final class JsonKey extends GenericJson { 46 | @Key("client_email") 47 | private String clientEmail; 48 | 49 | @Key("private_key") 50 | private String privateKey; 51 | 52 | public static JsonKey load(JsonFactory jsonFactory, InputStream inputStream) throws IOException { 53 | InputStreamReader reader = new InputStreamReader(inputStream, Charsets.UTF_8); 54 | try { 55 | Secret decoded = Secret.fromString(IOUtils.toString(reader)); 56 | return jsonFactory.fromString(decoded.getPlainText(), JsonKey.class); 57 | } finally { 58 | inputStream.close(); 59 | } 60 | } 61 | 62 | public String getClientEmail() { 63 | return clientEmail; 64 | } 65 | 66 | public void setClientEmail(String clientEmail) { 67 | this.clientEmail = clientEmail; 68 | } 69 | 70 | public String getPrivateKey() { 71 | return privateKey; 72 | } 73 | 74 | public void setPrivateKey(String privateKey) { 75 | this.privateKey = privateKey; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/JsonServiceAccountConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.cloudbees.plugins.credentials.SecretBytes; 19 | import com.google.api.client.json.jackson2.JacksonFactory; 20 | import com.google.api.client.util.PemReader; 21 | import com.google.api.client.util.Strings; 22 | import edu.umd.cs.findbugs.annotations.CheckForNull; 23 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 24 | import hudson.Extension; 25 | import java.io.ByteArrayInputStream; 26 | import java.io.IOException; 27 | import java.io.StringReader; 28 | import java.security.KeyFactory; 29 | import java.security.NoSuchAlgorithmException; 30 | import java.security.PrivateKey; 31 | import java.security.spec.InvalidKeySpecException; 32 | import java.security.spec.PKCS8EncodedKeySpec; 33 | import java.util.logging.Level; 34 | import java.util.logging.Logger; 35 | import jenkins.model.Jenkins; 36 | import org.apache.commons.fileupload.FileItem; 37 | import org.kohsuke.accmod.Restricted; 38 | import org.kohsuke.accmod.restrictions.DoNotUse; 39 | import org.kohsuke.stapler.DataBoundConstructor; 40 | import org.kohsuke.stapler.DataBoundSetter; 41 | 42 | /** 43 | * Provides authentication mechanism for a service account by setting a JSON private key file. The 44 | * JSON file structure needs to be: 45 | * 46 | *

47 | * { 48 | * "private_key":"-----BEGIN PRIVATE KEY-----\n 49 | * ... 50 | * \n-----END PRIVATE KEY-----\n", 51 | * "client_email":"...@developer.gserviceaccount.com", 52 | * ... 53 | * } 54 | * 55 | */ 56 | public class JsonServiceAccountConfig extends ServiceAccountConfig { 57 | /* 58 | * TODO(jenkinsci/google-oauth-plugin#50): Dedupe shared functionality in 59 | * google-auth-library. 60 | */ 61 | 62 | private static final long serialVersionUID = 6818111194672325387L; 63 | private static final Logger LOGGER = Logger.getLogger(JsonServiceAccountConfig.class.getSimpleName()); 64 | 65 | @CheckForNull 66 | private String filename; 67 | 68 | @CheckForNull 69 | private SecretBytes secretJsonKey; 70 | 71 | @Deprecated // for migration purpose 72 | @CheckForNull 73 | private transient String jsonKeyFile; 74 | 75 | private transient JsonKey jsonKey; 76 | 77 | /** @since 0.8 */ 78 | @DataBoundConstructor 79 | public JsonServiceAccountConfig() {} 80 | 81 | /** 82 | * For being able to load credentials created with versions < 0.8 and backwards compatibility 83 | * with external callers. 84 | * 85 | * @param jsonKeyFile The uploaded JSON key file. 86 | * @param prevJsonKeyFile The path of the previous JSON key file. 87 | * @since 0.3 88 | */ 89 | @Deprecated 90 | public JsonServiceAccountConfig(FileItem jsonKeyFile, String prevJsonKeyFile) { 91 | this.setJsonKeyFileUpload(jsonKeyFile); 92 | if (filename == null && prevJsonKeyFile != null) { 93 | this.filename = extractFilename(prevJsonKeyFile); 94 | this.secretJsonKey = getSecretBytesFromFile(prevJsonKeyFile); 95 | } 96 | } 97 | 98 | /** @param jsonKeyFileUpload The uploaded JSON key file. */ 99 | @DataBoundSetter // Called on form submit, only used when key file is uploaded 100 | public void setJsonKeyFileUpload(FileItem jsonKeyFileUpload) { 101 | if (jsonKeyFileUpload != null && jsonKeyFileUpload.getSize() > 0) { 102 | try { 103 | JsonKey jsonKey = JsonKey.load(new JacksonFactory(), jsonKeyFileUpload.getInputStream()); 104 | if (jsonKey.getClientEmail() != null && jsonKey.getPrivateKey() != null) { 105 | this.filename = extractFilename(jsonKeyFileUpload.getName()); 106 | this.secretJsonKey = SecretBytes.fromBytes(jsonKeyFileUpload.get()); 107 | } 108 | } catch (IOException e) { 109 | LOGGER.log(Level.SEVERE, "Failed to read JSON key from file", e); 110 | } 111 | } 112 | } 113 | 114 | /** @param filename The JSON key file name. */ 115 | @DataBoundSetter 116 | public void setFilename(String filename) { 117 | String newFilename = extractFilename(filename); 118 | if (!Strings.isNullOrEmpty(newFilename)) { 119 | this.filename = newFilename; 120 | } 121 | } 122 | 123 | /** @param secretJsonKey The JSON key file content. */ 124 | @DataBoundSetter 125 | public void setSecretJsonKey(SecretBytes secretJsonKey) { 126 | if (secretJsonKey != null && secretJsonKey.getPlainData().length > 0) { 127 | this.secretJsonKey = secretJsonKey; 128 | } 129 | } 130 | 131 | @CheckForNull 132 | private static String extractFilename(@CheckForNull String path) { 133 | if (path == null) { 134 | return null; 135 | } 136 | return path.replaceFirst("^.+[/\\\\]", ""); 137 | } 138 | 139 | @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") 140 | private Object readResolve() { 141 | if (secretJsonKey == null) { 142 | // google-oauth-plugin < 0.7 143 | return new JsonServiceAccountConfig(null, getJsonKeyFile()); 144 | } 145 | return this; 146 | } 147 | 148 | @Override 149 | public DescriptorImpl getDescriptor() { 150 | return (DescriptorImpl) Jenkins.get().getDescriptorOrDie(JsonServiceAccountConfig.class); 151 | } 152 | 153 | /** 154 | * @return Original uploaded file name. 155 | * @since 0.7 156 | */ 157 | @CheckForNull 158 | public String getFilename() { 159 | return filename; 160 | } 161 | 162 | @Restricted(DoNotUse.class) // UI: Required for stapler call of setter. 163 | @CheckForNull 164 | public SecretBytes getSecretJsonKey() { 165 | return secretJsonKey; 166 | } 167 | 168 | @Deprecated 169 | public String getJsonKeyFile() { 170 | return jsonKeyFile; 171 | } 172 | 173 | /** 174 | * For use in UI, do not use. 175 | * 176 | * @return The uploaded JSON key file. 177 | */ 178 | @Deprecated 179 | @Restricted(DoNotUse.class) // UI: Required for stapler call of setter. 180 | public FileItem getJsonKeyFileUpload() { 181 | return null; 182 | } 183 | 184 | /** 185 | * In this context the service account id is represented by the email address for that service 186 | * account, which should be contained in the JSON key. 187 | * 188 | * @return The service account identifier. Null if no JSON key has been provided. 189 | */ 190 | @Override 191 | public String getAccountId() { 192 | JsonKey jsonKey = getJsonKey(); 193 | if (jsonKey != null) { 194 | return jsonKey.getClientEmail(); 195 | } 196 | return null; 197 | } 198 | 199 | /** 200 | * @return The {@link PrivateKey} that comes from the secret JSON key. Null if this service 201 | * account config contains no key or if the key is malformed. 202 | */ 203 | @Override 204 | public PrivateKey getPrivateKey() { 205 | JsonKey jsonKey = getJsonKey(); 206 | if (jsonKey != null) { 207 | String privateKey = jsonKey.getPrivateKey(); 208 | if (privateKey != null && !privateKey.isEmpty()) { 209 | PemReader pemReader = new PemReader(new StringReader(privateKey)); 210 | try { 211 | PemReader.Section section = pemReader.readNextSection(); 212 | if (section != null) { 213 | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(section.getBase64DecodedBytes()); 214 | return KeyFactory.getInstance("RSA").generatePrivate(keySpec); 215 | } else { 216 | LOGGER.severe("The provided private key is malformed."); 217 | } 218 | } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { 219 | LOGGER.log(Level.SEVERE, "Failed to read private key", e); 220 | } 221 | } 222 | } 223 | return null; 224 | } 225 | 226 | private JsonKey getJsonKey() { 227 | if (jsonKey == null && secretJsonKey != null && secretJsonKey.getPlainData().length > 0) { 228 | try { 229 | jsonKey = JsonKey.load(new JacksonFactory(), new ByteArrayInputStream(secretJsonKey.getPlainData())); 230 | } catch (IOException ignored) { 231 | } 232 | } 233 | return jsonKey; 234 | } 235 | 236 | /** Descriptor for JSON service account authentication. */ 237 | @Extension 238 | public static final class DescriptorImpl extends Descriptor { 239 | @Override 240 | public String getDisplayName() { 241 | return Messages.JsonServiceAccountConfig_DisplayName(); 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/KeyUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import hudson.util.Secret; 19 | import java.io.File; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.nio.charset.StandardCharsets; 24 | import jenkins.model.Jenkins; 25 | import org.apache.commons.io.IOUtils; 26 | 27 | /** 28 | * Utility methods for handling key files. 29 | * 30 | * @deprecated Consider to use {@link com.cloudbees.plugins.credentials.SecretBytes} instead. 31 | */ 32 | @Deprecated 33 | public class KeyUtils { 34 | /** Utility class should never be instantiated. */ 35 | private KeyUtils() {} 36 | 37 | /** 38 | * Creates a file with the given prefix/suffix in a standard Google auth directory, and sets the 39 | * permissions of the file to owner-only read/write. Note: this doesn't work on Windows. 40 | * 41 | * @throws IOException if filesystem interaction fails. 42 | */ 43 | public static File createKeyFile(String prefix, String suffix) throws IOException { 44 | File keyFolder = new File(Jenkins.getInstance().getRootDir(), "gauth"); 45 | if (keyFolder.exists() || keyFolder.mkdirs()) { 46 | File result = File.createTempFile(prefix, suffix, keyFolder); 47 | if (result == null) { 48 | throw new IOException("Failed to create key file"); 49 | } 50 | updatePermissions(result); 51 | return result; 52 | } else { 53 | throw new IOException("Failed to create key folder"); 54 | } 55 | } 56 | 57 | /** 58 | * Sets the permissions of the file to owner-only read/write. Note: this doesn't work on Windows. 59 | * 60 | * @throws IOException if filesystem interaction fails. 61 | */ 62 | public static void updatePermissions(File file) throws IOException { 63 | if (file == null || !file.exists()) { 64 | return; 65 | } 66 | // Set world read/write permissions to false. 67 | // Set owner read/write permissions to true. 68 | if (!file.setReadable(false, false) 69 | || !file.setWritable(false, false) 70 | || !file.setReadable(true, true) 71 | || !file.setWritable(true, true)) { 72 | throw new IOException("Failed to update key file permissions"); 73 | } 74 | } 75 | 76 | /** 77 | * Writes the given key to the given keyfile, passing it through {@link Secret} to encode the 78 | * string. Note that, per the documentation of {@link Secret}, this does not protect against an 79 | * attacker who has full access to the local file system, but reduces the chance of accidental 80 | * exposure. 81 | */ 82 | public static void writeKeyToFileEncoded(String key, File file) throws IOException { 83 | if (key == null || file == null) { 84 | return; 85 | } 86 | Secret encoded = Secret.fromString(key); 87 | writeKeyToFile(IOUtils.toInputStream(encoded.getEncryptedValue(), StandardCharsets.UTF_8), file); 88 | } 89 | 90 | /** 91 | * Writes the key contained in the given {@link InputStream} to the given keyfile. Does not close 92 | * the input stream. 93 | */ 94 | public static void writeKeyToFile(InputStream keyStream, File file) throws IOException { 95 | if (keyStream == null || file == null) { 96 | return; 97 | } 98 | FileOutputStream out = null; 99 | try { 100 | out = new FileOutputStream(file); 101 | IOUtils.copy(keyStream, out); 102 | } finally { 103 | IOUtils.closeQuietly(out); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/LegacyJsonKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; 19 | import com.google.api.client.json.GenericJson; 20 | import com.google.api.client.json.JsonFactory; 21 | import com.google.api.client.util.Key; 22 | import com.google.common.base.Charsets; 23 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | 27 | /** 28 | * For "Robot" service account client secrets a key piece of information is the email address 29 | * contained within "client_secrets.json", which the existing {@link GoogleClientSecrets} class does 30 | * not parse. This makeshift partial copy of {@link GoogleClientSecrets} implements *just* the 31 | * "client_email" parsing. 32 | * 33 | * @author Matt Moore 34 | */ 35 | @Deprecated 36 | @SuppressWarnings("deprecation") 37 | @SuppressFBWarnings("EQ_DOESNT_OVERRIDE_EQUALS") 38 | public final class LegacyJsonKey extends GenericJson { 39 | 40 | /** Details for web applications. */ 41 | @Key 42 | private Details web; 43 | 44 | /** Returns the details for web applications. */ 45 | public Details getWeb() { 46 | return web; 47 | } 48 | 49 | public void setWeb(Details web) { 50 | this.web = web; 51 | } 52 | 53 | /** Container for our new field, modeled after: {@link GoogleClientSecrets.Details} */ 54 | public static final class Details extends GenericJson { 55 | /** Client email. */ 56 | @Key("client_email") 57 | private String clientEmail; 58 | 59 | public void setClientEmail(String clientEmail) { 60 | this.clientEmail = clientEmail; 61 | } 62 | 63 | /** Returns the client email. */ 64 | public String getClientEmail() { 65 | return clientEmail; 66 | } 67 | } 68 | 69 | /** Loads the {@code client_secrets.json} file from the given input stream. */ 70 | public static LegacyJsonKey load(JsonFactory jsonFactory, InputStream inputStream) throws IOException { 71 | return jsonFactory.fromInputStream(inputStream, Charsets.UTF_8, LegacyJsonKey.class); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/P12ServiceAccountConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.cloudbees.plugins.credentials.SecretBytes; 19 | import com.google.api.client.util.Strings; 20 | import edu.umd.cs.findbugs.annotations.CheckForNull; 21 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 22 | import hudson.Extension; 23 | import hudson.util.FormValidation; 24 | import java.io.ByteArrayInputStream; 25 | import java.io.IOException; 26 | import java.io.InputStream; 27 | import java.security.GeneralSecurityException; 28 | import java.security.KeyStore; 29 | import java.security.KeyStoreException; 30 | import java.security.NoSuchAlgorithmException; 31 | import java.security.PrivateKey; 32 | import java.security.cert.CertificateException; 33 | import java.util.logging.Level; 34 | import java.util.logging.Logger; 35 | import jenkins.model.Jenkins; 36 | import org.apache.commons.fileupload.FileItem; 37 | import org.apache.commons.io.IOUtils; 38 | import org.kohsuke.accmod.Restricted; 39 | import org.kohsuke.accmod.restrictions.DoNotUse; 40 | import org.kohsuke.stapler.DataBoundConstructor; 41 | import org.kohsuke.stapler.DataBoundSetter; 42 | import org.kohsuke.stapler.QueryParameter; 43 | import org.kohsuke.stapler.verb.POST; 44 | 45 | /** 46 | * Provides authentication mechanism for a service account by setting a service account email 47 | * address and P12 private key file. 48 | */ 49 | public class P12ServiceAccountConfig extends ServiceAccountConfig { 50 | /* 51 | * TODO(jenkinsci/google-oauth-plugin#50): Dedupe shared functionality in 52 | * google-auth-library. 53 | */ 54 | 55 | private static final long serialVersionUID = 8706353638974721795L; 56 | private static final Logger LOGGER = Logger.getLogger(P12ServiceAccountConfig.class.getSimpleName()); 57 | private static final String DEFAULT_P12_SECRET = "notasecret"; 58 | private static final String DEFAULT_P12_ALIAS = "privatekey"; 59 | private final String emailAddress; 60 | 61 | @CheckForNull 62 | private String filename; 63 | 64 | @CheckForNull 65 | private SecretBytes secretP12Key; 66 | 67 | @Deprecated // for migration purpose 68 | @CheckForNull 69 | private transient String p12KeyFile; 70 | 71 | /** 72 | * @param emailAddress The service account email address. 73 | * @since 0.8 74 | */ 75 | @DataBoundConstructor 76 | public P12ServiceAccountConfig(String emailAddress) { 77 | this.emailAddress = emailAddress; 78 | } 79 | 80 | /** 81 | * For being able to load credentials created with versions < 0.8 and backwards compatibility 82 | * with external callers. 83 | * 84 | * @param emailAddress The service account email address. 85 | * @param p12KeyFileUpload The uploaded p12 key file. 86 | * @param prevP12KeyFile The path of the previous p12 key file. 87 | * @since 0.3 88 | */ 89 | @Deprecated 90 | public P12ServiceAccountConfig(String emailAddress, FileItem p12KeyFileUpload, String prevP12KeyFile) { 91 | this(emailAddress); 92 | this.setP12KeyFileUpload(p12KeyFileUpload); 93 | if (filename == null && prevP12KeyFile != null) { 94 | this.setFilename(prevP12KeyFile); 95 | this.setSecretP12Key(getSecretBytesFromFile(prevP12KeyFile)); 96 | } 97 | } 98 | 99 | /** @param p12KeyFile The uploaded p12 key file. */ 100 | @Deprecated 101 | @DataBoundSetter // Called on form submit, only used when key file is uploaded 102 | public void setP12KeyFileUpload(FileItem p12KeyFile) { 103 | if (p12KeyFile != null && p12KeyFile.getSize() > 0) { 104 | this.filename = extractFilename(p12KeyFile.getName()); 105 | this.secretP12Key = SecretBytes.fromBytes(p12KeyFile.get()); 106 | } 107 | } 108 | 109 | /** @param filename The previous p12 key file name. */ 110 | @DataBoundSetter 111 | public void setFilename(String filename) { 112 | if (!Strings.isNullOrEmpty(filename)) { 113 | this.filename = extractFilename(filename); 114 | } 115 | } 116 | 117 | /** @param secretP12Key The previous p12 key file content. */ 118 | @DataBoundSetter 119 | public void setSecretP12Key(SecretBytes secretP12Key) { 120 | if (secretP12Key != null && secretP12Key.getPlainData().length > 0) { 121 | this.secretP12Key = secretP12Key; 122 | } 123 | } 124 | 125 | @CheckForNull 126 | private static String extractFilename(@CheckForNull String path) { 127 | if (path == null) { 128 | return null; 129 | } 130 | return path.replaceFirst("^.+[/\\\\]", ""); 131 | } 132 | 133 | @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") 134 | private Object readResolve() { 135 | if (secretP12Key == null) { 136 | // google-oauth-plugin < 0.7 137 | return new P12ServiceAccountConfig( 138 | getEmailAddress(), 139 | null, // p12KeyFileUpload 140 | getP12KeyFile()); 141 | } 142 | return this; 143 | } 144 | 145 | @Override 146 | public DescriptorImpl getDescriptor() { 147 | return (DescriptorImpl) Jenkins.get().getDescriptorOrDie(P12ServiceAccountConfig.class); 148 | } 149 | 150 | public String getEmailAddress() { 151 | return emailAddress; 152 | } 153 | 154 | /** 155 | * @return Original uploaded file name. 156 | * @since 0.7 157 | */ 158 | @CheckForNull 159 | public String getFilename() { 160 | return filename; 161 | } 162 | 163 | /** 164 | * Do not use, required for UI. 165 | * 166 | * @return The secret p12 key. 167 | */ 168 | @Restricted(DoNotUse.class) // UI: Required for stapler call of setter. 169 | @CheckForNull 170 | public SecretBytes getSecretP12Key() { 171 | return secretP12Key; 172 | } 173 | 174 | /** @return The path of the previous p12 key file. */ 175 | @Deprecated 176 | public String getP12KeyFile() { 177 | return p12KeyFile; 178 | } 179 | 180 | /** 181 | * Do not use, required for UI. 182 | * 183 | * @return The uploaded p12 key file. 184 | */ 185 | @Deprecated 186 | @Restricted(DoNotUse.class) // UI: Required for stapler call of setter. 187 | public FileItem getP12KeyFileUpload() { 188 | return null; 189 | } 190 | 191 | @Override 192 | public String getAccountId() { 193 | return getEmailAddress(); 194 | } 195 | 196 | @Override 197 | public PrivateKey getPrivateKey() { 198 | try { 199 | KeyStore p12KeyStore = getP12KeyStore(); 200 | if (p12KeyStore == null) { 201 | return null; 202 | } 203 | return (PrivateKey) p12KeyStore.getKey(DEFAULT_P12_ALIAS, DEFAULT_P12_SECRET.toCharArray()); 204 | } catch (IOException | GeneralSecurityException e) { 205 | LOGGER.log(Level.SEVERE, "Failed to read private key", e); 206 | } 207 | return null; 208 | } 209 | 210 | @CheckForNull 211 | private KeyStore getP12KeyStore() 212 | throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { 213 | InputStream in = null; 214 | if (secretP12Key == null) { 215 | return null; 216 | } 217 | try { 218 | KeyStore keyStore = KeyStore.getInstance("PKCS12"); 219 | in = new ByteArrayInputStream(secretP12Key.getPlainData()); 220 | keyStore.load(in, DEFAULT_P12_SECRET.toCharArray()); 221 | return keyStore; 222 | } finally { 223 | IOUtils.closeQuietly(in); 224 | } 225 | } 226 | 227 | /** Descriptor for P12 service account authentication. */ 228 | @Extension 229 | public static final class DescriptorImpl extends Descriptor { 230 | 231 | @POST 232 | public FormValidation doCheckEmailAddress(@QueryParameter("emailAddress") final String emailAddress) { 233 | if (Strings.isNullOrEmpty(emailAddress)) { 234 | return FormValidation.error(Messages.P12ServiceAccountConfig_ErrorEmailRequired()); 235 | } 236 | return FormValidation.ok(); 237 | } 238 | 239 | @Override 240 | public String getDisplayName() { 241 | return Messages.P12ServiceAccountConfig_DisplayName(); 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/RemotableGoogleCredentials.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | 20 | import com.cloudbees.plugins.credentials.CredentialsScope; 21 | import com.google.api.client.auth.oauth2.Credential; 22 | import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; 23 | import com.google.common.collect.Ordering; 24 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 25 | import java.io.IOException; 26 | import java.security.GeneralSecurityException; 27 | import org.joda.time.DateTime; 28 | 29 | /** 30 | * As some implementations of {@link GoogleRobotCredentials} are bound to the controller, this 31 | * ephemeral credential is remoted in place of those. The use case is basically that when a plugin 32 | * needs to remote credential C, with some requirement R, it would instead remote {@code 33 | * C.forRemote(R)} to instantiate one of these. 34 | * 35 | *

TODO(mattmoor): Consider ways to use channels to remove the time limitation that this has 36 | * (access token expires). 37 | * 38 | * @author Matt Moore 39 | */ 40 | final class RemotableGoogleCredentials extends GoogleRobotCredentials { 41 | /** 42 | * Construct a remotable credential. This should never be used directly, which is why this class 43 | * is {@code package-private}. This should only be called from {@link 44 | * GoogleRobotCredentials#forRemote}. 45 | */ 46 | @SuppressFBWarnings( 47 | value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", 48 | justification = "False positive from what I can see in Ordering.natural().nullsFirst()") 49 | public RemotableGoogleCredentials( 50 | GoogleRobotCredentials credentials, 51 | GoogleOAuth2ScopeRequirement requirement, 52 | GoogleRobotCredentialsModule module) 53 | throws GeneralSecurityException { 54 | super( 55 | credentials.getScope() == null ? CredentialsScope.GLOBAL : credentials.getScope(), 56 | "", 57 | checkNotNull(credentials).getProjectId(), 58 | checkNotNull(module)); 59 | 60 | this.username = credentials.getUsername(); 61 | 62 | // Eagerly create the access token we will use on the remote machine. 63 | Credential credential = credentials.getGoogleCredential(checkNotNull(requirement)); 64 | try { 65 | Long rawExpiration = credential.getExpiresInSeconds(); 66 | 67 | if (Ordering.natural().nullsFirst().compare(rawExpiration, MINIMUM_DURATION_SECONDS) < 0) { 68 | if (!credential.refreshToken()) { 69 | throw new GeneralSecurityException(Messages.RemotableGoogleCredentials_NoAccessToken()); 70 | } 71 | } 72 | } catch (IOException e) { 73 | throw new GeneralSecurityException(Messages.RemotableGoogleCredentials_NoAccessToken(), e); 74 | } 75 | this.accessToken = checkNotNull(credential.getAccessToken()); 76 | this.expiration = new DateTime() 77 | .plusSeconds(checkNotNull(credential.getExpiresInSeconds()).intValue()) 78 | .getMillis(); 79 | } 80 | /** 81 | * Construct a remotable credential. This should never be used directly - this constructor is only 82 | * for migrating old credentials that had no id and relied on the projectId during readResolve(). 83 | */ 84 | private RemotableGoogleCredentials( 85 | CredentialsScope scope, 86 | String id, 87 | String projectId, 88 | String description, 89 | GoogleRobotCredentialsModule module, 90 | String username, 91 | String accessToken, 92 | long expiration) { 93 | super(scope, id, projectId, description, module); 94 | this.username = username; 95 | this.accessToken = accessToken; 96 | this.expiration = expiration; 97 | } 98 | 99 | @SuppressFBWarnings( 100 | value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", 101 | justification = "for migrating older credentials that did not have a separate id field, and would really " 102 | + "have a null id when attempted to deserialize. readResolve overwrites these nulls") 103 | private Object readResolve() throws Exception { 104 | return new RemotableGoogleCredentials( 105 | getScope() == null ? CredentialsScope.GLOBAL : getScope(), 106 | getId() == null ? getProjectId() : getId(), 107 | getProjectId(), 108 | getDescription(), 109 | getModule(), 110 | username, 111 | accessToken, 112 | expiration); 113 | } 114 | 115 | /** {@inheritDoc} */ 116 | @Override 117 | public AbstractGoogleRobotCredentialsDescriptor getDescriptor() { 118 | throw new UnsupportedOperationException(Messages.RemotableGoogleCredentials_BadGetDescriptor()); 119 | } 120 | 121 | /** {@inheritDoc} */ 122 | @Override 123 | public String getUsername() { 124 | return username; 125 | } 126 | 127 | /** {@inheritDoc} */ 128 | @Override 129 | public Credential getGoogleCredential(GoogleOAuth2ScopeRequirement requirement) throws GeneralSecurityException { 130 | // Return a credential synthesized from our stored access token 131 | // and expiration. 132 | // 133 | // TODO(mattmoor): Consider throwing an exception if the access token 134 | // has expired. 135 | long lifetimeSeconds = (expiration - new DateTime().getMillis()) / 1000; 136 | 137 | return new GoogleCredential.Builder() 138 | .setTransport(getModule().getHttpTransport()) 139 | .setJsonFactory(getModule().getJsonFactory()) 140 | .build() 141 | .setAccessToken(accessToken) 142 | .setExpiresInSeconds(lifetimeSeconds); 143 | } 144 | 145 | /** The identity of the credential. */ 146 | private final String username; 147 | 148 | /** The access token eagerly retrieved from the original credential. */ 149 | private final String accessToken; 150 | 151 | /** The time at which the accessToken will expire. */ 152 | private final long expiration; 153 | 154 | /** 155 | * The minimum duration {@code 5 minutes} to allow for an access token before attempting to 156 | * refresh it. 157 | */ 158 | private static final Long MINIMUM_DURATION_SECONDS = 300L; 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/ServiceAccountConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.cloudbees.plugins.credentials.SecretBytes; 19 | import com.google.common.base.Strings; 20 | import edu.umd.cs.findbugs.annotations.CheckForNull; 21 | import hudson.model.Describable; 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.io.Serializable; 25 | import java.security.PrivateKey; 26 | import java.util.logging.Level; 27 | import java.util.logging.Logger; 28 | import jenkins.model.Jenkins; 29 | import org.apache.commons.io.FileUtils; 30 | 31 | /** 32 | * general abstraction for providing google service account authentication mechanism. subclasses 33 | * need to provide an accountId and a private key to use for authenticating a service account 34 | */ 35 | public abstract class ServiceAccountConfig implements Describable, Serializable { 36 | private static final Logger LOGGER = Logger.getLogger(ServiceAccountConfig.class.getName()); 37 | private static final long serialVersionUID = 6355493019938144806L; 38 | 39 | public abstract String getAccountId(); 40 | 41 | public abstract PrivateKey getPrivateKey(); 42 | 43 | @Deprecated // Used only for compatibility purposes. 44 | @CheckForNull 45 | protected SecretBytes getSecretBytesFromFile(@CheckForNull String filePath) { 46 | Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS); 47 | 48 | if (Strings.isNullOrEmpty(filePath)) { 49 | LOGGER.log(Level.SEVERE, "Provided file path is null or empty."); 50 | return null; 51 | } 52 | 53 | try { 54 | return SecretBytes.fromBytes(FileUtils.readFileToByteArray(new File(filePath))); 55 | } catch (IOException e) { 56 | LOGGER.log(Level.SEVERE, String.format("Failed to read previous key from %s", filePath), e); 57 | return null; 58 | } 59 | } 60 | 61 | /** abstract descriptor for service account authentication */ 62 | public abstract static class Descriptor extends hudson.model.Descriptor {} 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/credentials/oauth/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * This package implements Jenkins plugins providing Google-specific OAuth2 Credentials, Domain 19 | * Requirements and Specifications. 20 | * 21 | *

For OAuth2, these are inherently a provider-specific triple because each provider (e.g. 22 | * Google, Facebook, GitHub) may only provide tokens for their own credentials and scopes. In a 23 | * nutshell, an OAuth2 access token is like "limited power of attorney". You are giving the bearer 24 | * of that token permission to interact with the set of limited scopes as the user who provided it. 25 | * 26 | *

This package provides the following Google-specific triple: 27 | * 28 | *

    29 | *
  1. 30 | *
    GoogleOAuth2ScopeRequirement
    31 |  *  extends OAuth2ScopeRequirement
    32 |  *  extends DomainRequirement
    33 | *
  2. 34 | *
    GoogleOAuth2ScopeSpecification
    35 |  *  extends OAuth2ScopeSpecification<GoogleOAuth2ScopeRequirement>
    36 |  *  extends DomainSpecification
    37 | *
  3. 38 | *
    GoogleOAuth2Credentials
    39 |  *  extends OAuth2Credentials<GoogleOAuth2ScopeRequirement>
    40 |  *  extends Credentials
    41 | *
42 | * 43 | *

As the set of scopes determine what you may do with a credential, each plugin asks for an 44 | * access token by providing a provider-specific {@code OAuth2ScopeRequirement} to {@code 45 | * OAuth2Credentials.getAccessToken(OAuth2ScopeRequirement)}. 46 | * 47 | *

When enumerating credentials suitable for use with a given plugin, we only want to show those 48 | * that allow a suitable set of scopes. This is where {@code OAuth2ScopeRequirement} pairs with 49 | * {@code OAuth2ScopeSpecification}. An {@code OAuth2ScopeSpecification} is attached to a {@code 50 | * Domain} and is the superset of scopes to which the contained {@code Credentials} may be applied. 51 | * 52 | *

However, since entering OAuth2 scopes is unwieldy, we provide the necessary concepts to make 53 | * it multiple choice. Enter {@code DomainRequirementProvider}, a new {@code ExtensionPoint} that 54 | * allows {@code OAuth2ScopeSpecification} to automatically discover the set of OAuth2 scopes 55 | * required by installed plugins. 56 | * 57 | *

For Example:
58 | * 59 | *

60 |  * {@literal @}RequiredDomain(value = MyGoogleOAuth2Requirement.class)
61 |  * public class Foo extends SomeDescribable
62 |  * 
63 | * 64 | * In this example, the {@code DescribableDomainRequirementProvider} would discover that {@code Foo} 65 | * required the set of scopes specified by {@code MyGoogleOAuth2Requirement}. These would be 66 | * aggregated with any other required scopes and presented in the UI for any {@code 67 | * OAuth2ScopeSpecification} whose type parameter is a super-type of {@code 68 | * MyGoogleOAuth2Requirement}. 69 | * 70 | *

So for instance if {@code MyGoogleOAuth2Requirement extends} {@code 71 | * GoogleOAuth2ScopeRequirement} then {@code GoogleOAuth2ScopeSpecification}, which {@code extends} 72 | * {@code OAuth2ScopeSpecification}, would have {@code 73 | * MyGoogleOAuth2Requirement}'s scopes appear in its UI. 74 | * 75 | *

This package provides two types of {@code GoogleOAuth2Credentials}: 76 | * 77 | *

83 | */ 84 | package com.google.jenkins.plugins.credentials.oauth; 85 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/util/ConflictException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | /** This exception is used to wrap and propagate 409 (Conflict) HTTP exceptions up the stack. */ 19 | public class ConflictException extends ExecutorException { 20 | public ConflictException(Throwable throwable) { 21 | super(throwable); 22 | } 23 | 24 | public ConflictException() {} 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/util/Executor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_FORBIDDEN; 19 | import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_NOT_FOUND; 20 | import static com.google.common.base.Preconditions.checkNotNull; 21 | import static java.util.logging.Level.SEVERE; 22 | 23 | import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest; 24 | import com.google.api.client.http.HttpResponseException; 25 | import com.google.common.util.concurrent.Uninterruptibles; 26 | import java.io.IOException; 27 | import java.net.SocketTimeoutException; 28 | import java.util.concurrent.TimeUnit; 29 | import java.util.logging.Logger; 30 | 31 | /** 32 | * Interface for a class that executes requests on behalf of a Json API client. 33 | * 34 | *

NOTE: This can be used to intercept or mock all "execute" requests. 35 | */ 36 | public abstract class Executor { 37 | private static final Logger logger = Logger.getLogger(Executor.class.getName()); 38 | 39 | /** 40 | * Executes the request, returning a response of the appropriate type. 41 | * 42 | * @param The type of the expected response 43 | * @param request The request we are issuing 44 | * @return a Json object of the given type 45 | * @throws IOException if anything goes wrong 46 | */ 47 | public T execute(final AbstractGoogleJsonClientRequest request) throws IOException, ExecutorException { 48 | return execute(RequestCallable.from(request)); 49 | } 50 | 51 | /** 52 | * Executes the request, returning a response of the appropriate type. 53 | * 54 | * @param The type of the expected response 55 | * @param request The request we are issuing 56 | * @return a Json object of the given type 57 | * @throws IOException if anything goes wrong 58 | */ 59 | public abstract T execute(RequestCallable request) throws IOException, ExecutorException; 60 | 61 | /** 62 | * Surface this as a canonical means by which to sleep, so that clients can layer their own retry 63 | * logic on top of the executor using the same sleep facility; 64 | */ 65 | public void sleep() { 66 | Uninterruptibles.sleepUninterruptibly(SLEEP_DURATION_SECONDS, TimeUnit.SECONDS); 67 | } 68 | 69 | /** 70 | * Surface this as a canonical means by which to sleep, so that clients can layer their own retry 71 | * logic on top of the executor using the same sleep facility; 72 | * 73 | * @param retryAttempt indicates how many times we had retried, to allow for increasing back-off 74 | * time. 75 | */ 76 | public void sleep(int retryAttempt) { 77 | sleep(); 78 | } 79 | 80 | /** Seconds to sleep between API request retry attempts */ 81 | private static final long SLEEP_DURATION_SECONDS = 15; 82 | 83 | /** A default, failure-tolerant implementation of the {@link Executor} class. */ 84 | public static class Default extends Executor { 85 | public Default() { 86 | this(RETRY_COUNT, true /* compose retry */); 87 | } 88 | 89 | /** 90 | * @param maxRetry the maximum number of retries to attempt in {@link 91 | * #execute(RequestCallable)}. 92 | * @param composeRetry whether nested retries block cause retries to compose or not. If set to 93 | * false, we will wrap the exception of the last retry step in an instance of {@link 94 | * MaxRetryExceededException}, which prevents any further retries. 95 | */ 96 | public Default(int maxRetry, boolean composeRetry) { 97 | this.maxRetry = maxRetry; 98 | this.composeRetry = composeRetry; 99 | } 100 | 101 | private boolean composeRetry; 102 | 103 | private int getMaxRetry() { 104 | return maxRetry; 105 | } 106 | 107 | private final int maxRetry; 108 | 109 | private IOException propagateRetry(IOException lastException) throws IOException, ExecutorException { 110 | if (composeRetry) { 111 | throw lastException; 112 | } else { 113 | throw new MaxRetryExceededException(lastException); 114 | } 115 | } 116 | 117 | /** {@inheritDoc} */ 118 | @Override 119 | public T execute(RequestCallable block) throws IOException, ExecutorException { 120 | IOException lastException = null; 121 | for (int i = 0; i < getMaxRetry(); ++i) { 122 | try { 123 | return checkNotNull(block).call(); 124 | } catch (HttpResponseException e) { 125 | lastException = e; 126 | // Wrap a set of exception conditions, which when returned from 127 | // Google APIs are indicative of a state that is unlikely to 128 | // change. 129 | if (e.getStatusCode() == STATUS_CODE_NOT_FOUND) { 130 | throw new NotFoundException(e); 131 | } 132 | if (e.getStatusCode() == STATUS_CODE_FORBIDDEN) { 133 | throw new ForbiddenException(e); 134 | } 135 | if (e.getStatusCode() == 409 /* STATUS_CODE_CONFLICT */) { 136 | throw new ConflictException(e); 137 | } 138 | // Many other status codes may simply relate to ephemeral 139 | // service availability hiccups, that could simply go away 140 | // on retry. 141 | logger.log(SEVERE, Messages.Executor_HttpError(), e); 142 | } catch (SocketTimeoutException e) { 143 | logger.log(SEVERE, Messages.Executor_TimeoutError(), e); 144 | lastException = e; 145 | } 146 | 147 | if (!block.canRetry()) { 148 | // If this request contained a media upload, then it cannot simply 149 | // be retried. 150 | break; 151 | } 152 | // Pause before we retry 153 | sleep(i); 154 | } 155 | throw propagateRetry(checkNotNull(lastException)); 156 | } 157 | 158 | private static final int RETRY_COUNT = 5; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/util/ExecutorException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | /** 19 | * This base exception class is used to wrap and propagate HTTP exceptions for specific status codes 20 | * up the stack. We distinguish this kind from {@link java.io.IOException} so that all other kinds 21 | * of exceptions may be safely handled while letting these propagate. 22 | */ 23 | public abstract class ExecutorException extends Exception { 24 | public ExecutorException(Throwable throwable) { 25 | super(throwable); 26 | } 27 | 28 | public ExecutorException() {} 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/util/ForbiddenException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | /** This exception is used to wrap and propagate 403 (Forbidden) HTTP exceptions up the stack. */ 19 | public class ForbiddenException extends ExecutorException { 20 | public ForbiddenException(Throwable throwable) { 21 | super(throwable); 22 | } 23 | 24 | public ForbiddenException() {} 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/util/MaxRetryExceededException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | /** 19 | * This exception is used to signify that the maximum number of retries has been exceeded, that we 20 | * shouldn't make further attempt. 21 | */ 22 | public class MaxRetryExceededException extends ExecutorException { 23 | public MaxRetryExceededException(Throwable throwable) { 24 | super(throwable); 25 | } 26 | 27 | public MaxRetryExceededException() {} 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/util/MetadataReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_FORBIDDEN; 19 | import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_NOT_FOUND; 20 | import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_UNAUTHORIZED; 21 | import static com.google.common.base.Preconditions.checkNotNull; 22 | 23 | import com.google.api.client.http.GenericUrl; 24 | import com.google.api.client.http.HttpRequest; 25 | import com.google.api.client.http.HttpRequestFactory; 26 | import com.google.api.client.http.HttpResponse; 27 | import com.google.api.client.http.HttpResponseException; 28 | import com.google.api.client.http.javanet.NetHttpTransport; 29 | import com.google.common.base.Charsets; 30 | import java.io.IOException; 31 | import java.io.InputStreamReader; 32 | import java.io.StringWriter; 33 | import org.apache.commons.io.IOUtils; 34 | 35 | /** 36 | * This helper utility is used for reading values out of a Google Compute Engine instance's attached 37 | * metadata service. 38 | * 39 | * @author Matt Moore 40 | */ 41 | public interface MetadataReader { 42 | /** Are we on a Google Compute Engine instance? */ 43 | boolean hasMetadata() throws IOException; 44 | 45 | /** 46 | * Reads the specified sub-element out of the Google Compute Engine instance's metadata. These 47 | * relative paths are expected to start with: 48 | * 49 | *

53 | */ 54 | String readMetadata(String metadataPath) throws IOException, ExecutorException; 55 | 56 | /** A simple default implementation that reads metadata via http requests. */ 57 | public static class Default implements MetadataReader { 58 | public Default() { 59 | this(new NetHttpTransport().createRequestFactory()); 60 | } 61 | 62 | public Default(HttpRequestFactory requestFactory) { 63 | this.requestFactory = checkNotNull(requestFactory); 64 | } 65 | 66 | private final HttpRequestFactory requestFactory; 67 | 68 | /** {@inheritDoc} */ 69 | @Override 70 | public String readMetadata(String metadataPath) throws IOException, ExecutorException { 71 | HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(METADATA_SERVER + metadataPath)); 72 | 73 | // GCE v1 requires requests to the metadata service to specify 74 | // this header in order to get anything back. 75 | request.getHeaders().set("Metadata-Flavor", "Google"); 76 | 77 | HttpResponse response; 78 | try { 79 | response = request.execute(); 80 | } catch (HttpResponseException e) { 81 | switch (e.getStatusCode()) { 82 | case STATUS_CODE_UNAUTHORIZED: 83 | case STATUS_CODE_FORBIDDEN: 84 | throw new ForbiddenException(e); 85 | case STATUS_CODE_NOT_FOUND: 86 | throw new NotFoundException(e); 87 | default: 88 | throw e; 89 | } 90 | } 91 | 92 | try (InputStreamReader inChars = 93 | new InputStreamReader(checkNotNull(response.getContent()), Charsets.UTF_8)) { 94 | StringWriter output = new StringWriter(); 95 | IOUtils.copy(inChars, output); 96 | return output.toString(); 97 | } 98 | } 99 | 100 | /** {@inheritDoc} */ 101 | @Override 102 | public boolean hasMetadata() { 103 | try { 104 | readMetadata(""); 105 | return true; 106 | } catch (IOException | ExecutorException e) { 107 | return false; 108 | } 109 | } 110 | 111 | /** 112 | * The address of the GCE Metadata service that provides GCE instances with information about 113 | * the default service account. 114 | */ 115 | public static final String METADATA_SERVER = "http://metadata/computeMetadata/v1"; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/util/MockExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | 20 | import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest; 21 | import com.google.common.base.Predicate; 22 | import com.google.common.base.Predicates; 23 | import java.io.IOException; 24 | import java.util.LinkedList; 25 | 26 | /** 27 | * This is an implementation of {@link Executor} that can be injected to inject a set of canned 28 | * responses to requests including: 29 | * 30 | * 35 | */ 36 | public class MockExecutor extends Executor { 37 | public MockExecutor() { 38 | requestTypes = new LinkedList<>(); 39 | responses = new LinkedList<>(); 40 | exceptions = new LinkedList<>(); 41 | predicates = new LinkedList<>(); 42 | sawUnexpected = false; 43 | } 44 | 45 | /** {@inheritDoc} */ 46 | @Override 47 | public void sleep() { 48 | // Never sleep, this is a test library, we want fast tests. 49 | } 50 | 51 | /** {@inheritDoc} */ 52 | @Override 53 | public T execute(RequestCallable request) throws IOException, ExecutorException { 54 | // TODO(nghia): Think about implementing this. 55 | throw new UnsupportedOperationException(); 56 | } 57 | 58 | /** {@inheritDoc} */ 59 | @Override 60 | public T execute(AbstractGoogleJsonClientRequest request) throws IOException, ExecutorException { 61 | Class requestClass = request.getClass(); 62 | if (requestTypes.isEmpty()) { 63 | sawUnexpected = true; 64 | throw new IllegalStateException("Unexpected request: " + requestClass); 65 | } 66 | 67 | // Remove all three states to keep the lists in sync 68 | Class clazz = requestTypes.removeFirst(); 69 | Object response = responses.removeFirst(); 70 | Exception exception = exceptions.removeFirst(); 71 | Predicate> predicate = 72 | (Predicate>) predicates.removeFirst(); 73 | 74 | if (requestClass != clazz) { 75 | sawUnexpected = true; 76 | throw new IllegalStateException( 77 | "Unexpected (or out of order) request: " + requestClass + " expected: " + clazz); 78 | } 79 | 80 | if (!predicate.apply(request)) { 81 | sawUnexpected = true; 82 | throw new IllegalStateException("User predicate: " + predicate + " failed for request: " + requestClass); 83 | } 84 | 85 | if (response == null) { 86 | if (exception != null) { 87 | if (exception instanceof IOException) { 88 | throw (IOException) exception; // throwWhen(IOException) 89 | } else { 90 | throw (ExecutorException) exception; // throwWhen(ExecutorException) 91 | } 92 | } 93 | return (T) request.getJsonContent(); // passThruWhen 94 | } 95 | return (T) response; // when 96 | } 97 | 98 | /** 99 | * When the next request matches the given {@code requestType} and the provided user {@link 100 | * Predicate} return {@code response} as the response. 101 | */ 102 | public , C extends S> void when( 103 | Class requestType, T response, Predicate predicate) { 104 | requestTypes.add(checkNotNull(requestType)); 105 | responses.add(response); // must allow null for delete's Void return type 106 | exceptions.add(null); 107 | predicates.add(checkNotNull(predicate)); 108 | } 109 | 110 | /** 111 | * When the next request matches the given {@code requestType} return {@code response} as the 112 | * response. 113 | */ 114 | public > void when(Class requestType, T response) { 115 | when(requestType, response, Predicates.alwaysTrue()); 116 | } 117 | 118 | /** 119 | * When the next request matches the given {@code requestType} and the provided user {@link 120 | * Predicate} throw {@code exception} instead of responding. 121 | */ 122 | public , C extends S> void throwWhen( 123 | Class requestType, IOException exception, Predicate predicate) { 124 | throwWhenInternal(requestType, exception, predicate); 125 | } 126 | 127 | /** 128 | * When the next request matches the given {@code requestType} throw {@code exception} instead of 129 | * responding. 130 | */ 131 | public > void throwWhen( 132 | Class requestType, IOException exception) { 133 | throwWhen(requestType, exception, Predicates.alwaysTrue()); 134 | } 135 | 136 | /** 137 | * When the next request matches the given {@code requestType} and the provided user {@link 138 | * Predicate} throw {@code exception} instead of responding. 139 | */ 140 | public , C extends S> void throwWhen( 141 | Class requestType, ExecutorException exception, Predicate predicate) { 142 | throwWhenInternal(requestType, exception, predicate); 143 | } 144 | 145 | /** 146 | * When the next request matches the given {@code requestType} throw {@code exception} instead of 147 | * responding. 148 | */ 149 | public > void throwWhen( 150 | Class requestType, ExecutorException exception) { 151 | throwWhen(requestType, exception, Predicates.alwaysTrue()); 152 | } 153 | 154 | /** 155 | * When the next request matches the given {@code requestType} and the provided user {@link 156 | * Predicate} throw {@code exception} instead of responding. 157 | */ 158 | private , C extends S> void throwWhenInternal( 159 | Class requestType, Exception exception, Predicate predicate) { 160 | requestTypes.add(checkNotNull(requestType)); 161 | responses.add(null); 162 | exceptions.add(exception); 163 | predicates.add(checkNotNull(predicate)); 164 | } 165 | 166 | /** 167 | * When the next request matches the given {@code requestType} and the provided user {@link 168 | * Predicate} pass through the request's {@code getJsonContent()} cast to the expected response 169 | * type. 170 | */ 171 | public , C extends S> void passThruWhen( 172 | Class requestType, Predicate predicate) { 173 | requestTypes.add(checkNotNull(requestType)); 174 | responses.add(null); 175 | exceptions.add(null); 176 | predicates.add(checkNotNull(predicate)); 177 | } 178 | 179 | /** 180 | * When the next request matches the given {@code requestType} pass through the request's {@code 181 | * getJsonContent()} cast to the expected response type. 182 | */ 183 | public > void passThruWhen(Class requestType) { 184 | passThruWhen(requestType, Predicates.alwaysTrue()); 185 | } 186 | 187 | /** Did we see all of the expected requests? */ 188 | public boolean sawAll() { 189 | return requestTypes.isEmpty(); 190 | } 191 | 192 | /** Did we see any unexpected requests? */ 193 | public boolean sawUnexpected() { 194 | return sawUnexpected; 195 | } 196 | 197 | private final LinkedList> requestTypes; 198 | private final LinkedList responses; 199 | private final LinkedList exceptions; 200 | private final LinkedList> predicates; 201 | private boolean sawUnexpected; 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/util/NameValuePair.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | 20 | /** 21 | * A name-value pair helper class. 22 | * 23 | * @param The type for the name 24 | * @param The type for the value 25 | */ 26 | public class NameValuePair { 27 | /** Construct a pair from the given name and value. */ 28 | public NameValuePair(N name, V value) { 29 | this.name = checkNotNull(name); 30 | this.value = checkNotNull(value); 31 | } 32 | 33 | /** Fetches the name */ 34 | public N getName() { 35 | return this.name; 36 | } 37 | 38 | /** Fetches the value */ 39 | public V getValue() { 40 | return this.value; 41 | } 42 | 43 | private final N name; 44 | private final V value; 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/util/NotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | /** This exception is used to wrap and propagate 404 (Not Found) HTTP exceptions up the stack. */ 19 | public class NotFoundException extends ExecutorException { 20 | public NotFoundException(Throwable throwable) { 21 | super(throwable); 22 | } 23 | 24 | public NotFoundException() {} 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/util/RequestCallable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.jenkins.plugins.util; 18 | 19 | import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest; 20 | import java.io.IOException; 21 | import java.util.concurrent.Callable; 22 | 23 | /** 24 | * A {@link Callable} that only throws {@link IOException} or {@link ExecutorException}. 25 | * 26 | * @param the return type for the request. 27 | */ 28 | public abstract class RequestCallable implements Callable { 29 | 30 | /** {@inheritDoc} */ 31 | @Override 32 | public abstract T call() throws IOException, ExecutorException; 33 | 34 | /** @return whether this request can be retry. */ 35 | public boolean canRetry() { 36 | return true; 37 | } 38 | 39 | /** @return a {@link RequestCallable} that executes a request. */ 40 | public static RequestCallable from(final AbstractGoogleJsonClientRequest request) { 41 | return new RequestCallable() { 42 | 43 | /** {@inheritDoc} */ 44 | @Override 45 | public R call() throws IOException { 46 | return request.execute(); 47 | } 48 | 49 | /** {@inheritDoc} */ 50 | @Override 51 | public boolean canRetry() { 52 | return (request.getMediaHttpUploader() == null); 53 | } 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/google/jenkins/plugins/util/Resolve.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | 20 | import hudson.Util; 21 | import java.util.Collections; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | /** 26 | * Container class for static methods that resolve form entries with typical form data, for use in 27 | * Jenkins {@code doCheckFoo} methods. 28 | * 29 | * @author Matt Moore 30 | */ 31 | public final class Resolve { 32 | /** 33 | * Replaces Jenkins build variables (e.g. {@code $BUILD_NUMBER}) with sample values, so the result 34 | * can be form-validated. 35 | * 36 | * @param input The unresolved form input string 37 | * @return the string with substitutions made for built-in variables. 38 | */ 39 | public static String resolveBuiltin(String input) { 40 | return resolveBuiltinWithCustom(checkNotNull(input), Collections.emptyMap()); 41 | } 42 | 43 | /** 44 | * Replaces Jenkins build variables (e.g. {@code $BUILD_NUMBER}) and custom user variables (e.g. 45 | * {@code $foo}) with sample values, so the result can be form-validated. 46 | * 47 | * @param input The unresolved form input string 48 | * @param customEnvironment The sample variable values to resolve 49 | * @return the string with substitutions made for built-in variables. 50 | */ 51 | public static String resolveBuiltinWithCustom(String input, Map customEnvironment) { 52 | checkNotNull(input); 53 | checkNotNull(customEnvironment); 54 | 55 | // Combine customEnvironment and sampleEnvironment into a new map. 56 | Map combinedEnvironment = new HashMap<>(); 57 | combinedEnvironment.putAll(defaultValues); 58 | combinedEnvironment.putAll(customEnvironment); // allow overriding defaults 59 | 60 | return resolveCustom(input, combinedEnvironment); 61 | } 62 | 63 | /** 64 | * Replaces a user's custom Jenkins variables (e.g. {@code $foo}) with provided sample values, so 65 | * the result can be form-validated. 66 | * 67 | * @param input The unresolved form input string 68 | * @param customEnvironment The sample variable values to resolve 69 | * @return the string with substitutions made for variables. 70 | */ 71 | public static String resolveCustom(String input, Map customEnvironment) { 72 | checkNotNull(input); 73 | checkNotNull(customEnvironment); 74 | 75 | return Util.replaceMacro(input, customEnvironment); 76 | } 77 | 78 | private static Map defaultValues = new HashMap<>(); 79 | 80 | // See: http://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project 81 | static { 82 | defaultValues.put("BUILD_NUMBER", "42"); 83 | defaultValues.put("BUILD_ID", "2005-08-22_23-59-59"); 84 | defaultValues.put("BUILD_URL", "http://buildserver/jenkins/job/MyJobName/666/"); 85 | defaultValues.put("NODE_NAME", "master"); 86 | defaultValues.put("JOB_NAME", "hello world"); 87 | defaultValues.put("BUILD_TAG", "jenkins-job name-42"); 88 | defaultValues.put("JENKINS_URL", "https://build.mydomain.org/"); 89 | defaultValues.put("EXECUTOR_NUMBER", "3"); 90 | defaultValues.put("JAVA_HOME", "/usr/bin/"); 91 | defaultValues.put("WORKSPACE", "/tmp/jenkins/"); 92 | defaultValues.put("SVN_REVISION", "r1234"); 93 | defaultValues.put("CVS_BRANCH", "TODO"); 94 | defaultValues.put("GIT_COMMIT", "a48b5b3273a1"); 95 | defaultValues.put("GIT_BRANCH", "origin/master"); 96 | 97 | defaultValues = Collections.unmodifiableMap(defaultValues); 98 | } 99 | 100 | /** Not intended for instantiation. */ 101 | private Resolve() { 102 | throw new UnsupportedOperationException("Not intended for instantiation"); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/credentials/oauth/GoogleRobotMetadataCredentials/config.jelly: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ${%NOTE:} ${%This instance is limited to accessing:} 28 |
    29 | 30 |
  • ${scope}
  • 31 |
    32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/credentials/oauth/GoogleRobotMetadataCredentials/help-projectId.html: -------------------------------------------------------------------------------- 1 | 16 |
17 |

18 | This is the Google Cloud Console project name. 19 | 20 |

21 |
22 | -------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/credentials/oauth/GoogleRobotPrivateKeyCredentials/config.jelly: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 30 | 33 | 36 | 40 | 41 | 42 | 43 | 45 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/credentials/oauth/GoogleRobotPrivateKeyCredentials/help-credentials.html: -------------------------------------------------------------------------------- 1 | 16 |
17 |

18 | The set of Google credentials registered with the Jenkins Credential Manager for authenticating with your project. This can be configured under "Manage Jenkins" -> "Manage Credentials".

19 | NOTE: only suitable credentials will be listed. If nothing is showing up, please verify that your "Google Limited Service Account" has all the necessary scopes for this plugin (recommended). If you intend to use a "Google Service Account" directly, ensure you have checked the "Allow Unlimited Service Account Usage" check-box under advanced (not recommended). 20 |

21 |
22 | -------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/credentials/oauth/GoogleRobotPrivateKeyCredentials/help-projectId.html: -------------------------------------------------------------------------------- 1 | 16 |
17 |

18 | This is the Google Cloud Console project name. 19 | 20 |

21 |
22 | -------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/credentials/oauth/JsonServiceAccountConfig/config.jelly: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 30 | 31 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/credentials/oauth/JsonServiceAccountConfig/help-jsonKeyFileUpload.html: -------------------------------------------------------------------------------- 1 | 16 |
17 |

18 | This is the "{project name}-xxxxxxxxxxxx.json" file associated with the service account credential in 19 | the Google Developer Console. A new private key may be generated 20 | for a service account if you no longer have the original. 21 | 22 |

23 | To create a new service account:

24 |
    25 |
  • Select your project from the Project list.
  • 26 |
  • Navigate to "APIs & Auth" and then "Credentials"
  • 27 |
  • Click the "Create new Client ID" button
  • 28 |
  • Select "Service account" and click "Create Client ID".
  • 29 |
  • A dialog should pop-up to download the private key file for the new service account, save this in a secure 30 | location. This is the file you must upload here.
  • 31 |
  • After clicking, "Okay, got it", you should see a "Service Account" section; in order to generate a new 32 | Private key, click on the "Generate new JSON key" button just underneath it.
  • 33 |
34 |

35 | To regenerate the client secrets file for an existing service account, follow the above instructions until the 36 | "Create new Client ID" step and instead find the Service Account for which you need a new key and click 37 | "Generate new JSON key".

38 |
39 | -------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/credentials/oauth/Messages.properties: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | GoogleRobotPrivateKeyCredentials.ExceptionString=An unknown problem occured while retrieving token 15 | GoogleRobotPrivateKeyCredentials.DisplayName=Google Service Account from private key 16 | GoogleRobotPrivateKeyCredentials.BadCredentials=An error occurred deducing a username from the provided credentials files. 17 | GoogleRobotPrivateKeyCredentials.ProjectIDError=A project name must be specified 18 | JsonServiceAccountConfig.DisplayName=JSON key 19 | P12ServiceAccountConfig.DisplayName=P12 key 20 | P12ServiceAccountConfig.ErrorEmailRequired=Email address is required 21 | GoogleRobotMetadataCredentials.DisplayName=Google Service Account from metadata 22 | GoogleRobotMetadataCredentials.ProjectIDError=A project name must be specified 23 | GoogleRobotMetadataCredentials.AddProjectIdAuthError=Insufficient privileges to add a project 24 | GoogleRobotMetadataCredentials.DefaultIdentityError=An unknown problem occured while retrieving the default service account\'s identity 25 | GoogleAuthorizationStrategy.DisplayName=Project Matrix Authorization Strategy (using Google Cloud Console roles) 26 | GoogleAuthorizationStrategy.CredentialError=Need to provide a credential 27 | RemotableGoogleCredentials.NoAccessToken=Unable to retrieve an access token with the provided credentials 28 | RemotableGoogleCredentials.BadGetName=Consumers of RemotableGoogleCredentials should not invoke getName 29 | RemotableGoogleCredentials.BadGetDescriptor=Consumers of RemotableGoogleCredentials should not invoke getDescriptor 30 | GoogleOAuth2ScopeSpecification.DisplayName=Google OAuth 2.0 Scope Specification 31 | GoogleRobotCredentials.Description=A Google robot account for accessing Google APIs and services. 32 | GoogleRobotCredentials.NoAnnotation={0} must be annotated with @RequiresDomain -------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/credentials/oauth/P12ServiceAccountConfig/config.jelly: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 36 | 37 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/credentials/oauth/P12ServiceAccountConfig/help-emailAddress.html: -------------------------------------------------------------------------------- 1 | 16 |
17 |

18 | This is the E-Mail address associated with the service account credential in the 19 | Google Developer Console. 20 | 21 |

22 | To create a new service account:

23 |
    24 |
  • Select your project from the Project list.
  • 25 |
  • Navigate to "APIs & Auth" and then "Credentials"
  • 26 |
  • Click the "Create new Client ID" button
  • 27 |
  • Select "Service account" and click "Create Client ID".
  • 28 |
  • A dialog should pop-up to download the private key file for the new service account, save this in a secure 29 | location. This file contains a Private key in json format.
  • 30 |
  • After clicking, "Okay, got it", you should see a "Service Account" section and a Email address field; This is 31 | the E-Mail address you must input here.
  • 32 |
33 |
-------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/credentials/oauth/P12ServiceAccountConfig/help-p12KeyFileUpload.html: -------------------------------------------------------------------------------- 1 | 16 |
17 |

18 | This is the "{project name}-xxxxxxxxxxxx.p12" file associated with the service account credential in 19 | the Google Developer Console. A new private key may be generated 20 | for a service account if you no longer have the original. 21 | 22 |

23 | To create a new service account:

24 |
    25 |
  • Select your project from the Project list.
  • 26 |
  • Navigate to "APIs & Auth" and then "Credentials"
  • 27 |
  • Click the "Create new Client ID" button
  • 28 |
  • Select "Service account" and click "Create Client ID".
  • 29 |
  • A dialog should pop-up to download the private key file for the new service account, save this in a secure 30 | location. This file contains a Private key in json format. This is NOT the file you must upload here.
  • 31 |
  • After clicking, "Okay, got it", you should see a "Service Account" section; click on the "Generate new P12 key" 32 | button just underneath it. This is the file you must upload here.
  • 33 |
34 |

35 | To generate a new key for an existing service account, follow the above instructions until the "Create new Client 36 | ID" step and instead find the Service Account for which you need a new key and click "Generate new P12 key".

37 |
38 | -------------------------------------------------------------------------------- /src/main/resources/com/google/jenkins/plugins/util/Messages.properties: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | Executor.HttpError=RPC failed on http response exception 15 | Executor.TimeoutError=RPC failed on socket timeout 16 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | This plugin implements the OAuth Credentials interfaces to surface Google Service Account credentials to Jenkins. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/lib/auth/blockWrapper.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 |
10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /src/main/resources/lib/auth/credentials.jelly: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | 21 | Inserts a <select> box for compatible Google credentials. 22 | 23 | 24 | A label for the credential input. 25 | 26 | 27 | Used for databinding. 28 | 29 | 30 | 38 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 53 | 54 | 55 | 56 | 57 |
58 | ${%You must register a Google Service Account with:} 59 |
60 |
61 |
62 | 63 | Scope(s): 64 | 65 | ${scope} 66 | 67 | 68 |
69 |
-------------------------------------------------------------------------------- /src/main/resources/lib/auth/taglib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/google-oauth-plugin/31633a5e08313b7871be6e4622b5632699628e98/src/main/resources/lib/auth/taglib -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/GoogleOAuthPluginTestSuite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins; 17 | 18 | import com.google.jenkins.plugins.CredentialsOAuthTestSuite; 19 | import com.google.jenkins.plugins.UtilTestSuite; 20 | import org.junit.runner.RunWith; 21 | import org.junit.runners.Suite; 22 | 23 | /** Defines the full test suite for the Google Oauth Plugin. */ 24 | @RunWith(Suite.class) 25 | @Suite.SuiteClasses(value = {CredentialsOAuthTestSuite.class, UtilTestSuite.class}) 26 | public class GoogleOAuthPluginTestSuite {} 27 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/CredentialsOAuthTestSuite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins; 17 | 18 | import com.google.jenkins.plugins.credentials.oauth.ConfigurationAsCodeTest; 19 | import com.google.jenkins.plugins.credentials.oauth.GoogleOAuth2ScopeSpecificationTest; 20 | import com.google.jenkins.plugins.credentials.oauth.GoogleRobotCredentialsTest; 21 | import com.google.jenkins.plugins.credentials.oauth.GoogleRobotMetadataCredentialsTest; 22 | import com.google.jenkins.plugins.credentials.oauth.GoogleRobotPrivateKeyCredentialsTest; 23 | import com.google.jenkins.plugins.credentials.oauth.JsonServiceAccountConfigTest; 24 | import com.google.jenkins.plugins.credentials.oauth.P12ServiceAccountConfigTest; 25 | import com.google.jenkins.plugins.credentials.oauth.RemotableGoogleCredentialsTest; 26 | import org.junit.runner.RunWith; 27 | import org.junit.runners.Suite; 28 | 29 | /** Defines the full test suite involving OAuth credentials. */ 30 | @RunWith(Suite.class) 31 | @Suite.SuiteClasses( 32 | value = { 33 | ConfigurationAsCodeTest.class, 34 | GoogleOAuth2ScopeSpecificationTest.class, 35 | GoogleRobotCredentialsTest.class, 36 | GoogleRobotMetadataCredentialsTest.class, 37 | GoogleRobotPrivateKeyCredentialsTest.class, 38 | JsonServiceAccountConfigTest.class, 39 | P12ServiceAccountConfigTest.class, 40 | RemotableGoogleCredentialsTest.class 41 | }) 42 | public class CredentialsOAuthTestSuite {} 43 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/UtilTestSuite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins; 17 | 18 | import com.google.jenkins.plugins.util.ExecutorTest; 19 | import com.google.jenkins.plugins.util.MetadataReaderTest; 20 | import com.google.jenkins.plugins.util.MockExecutorTest; 21 | import com.google.jenkins.plugins.util.NameValuePairTest; 22 | import com.google.jenkins.plugins.util.ResolveTest; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.Suite; 25 | 26 | /** Defines the full test suite for utility classes. */ 27 | @RunWith(Suite.class) 28 | @Suite.SuiteClasses( 29 | value = { 30 | ExecutorTest.class, 31 | MetadataReaderTest.class, 32 | MockExecutorTest.class, 33 | NameValuePairTest.class, 34 | ResolveTest.class 35 | }) 36 | public class UtilTestSuite {} 37 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/ConfigurationAsCodeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.jenkins.plugins.credentials.oauth; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertNotNull; 21 | import static org.junit.Assert.assertNull; 22 | 23 | import com.cloudbees.plugins.credentials.CredentialsProvider; 24 | import com.cloudbees.plugins.credentials.SecretBytes; 25 | import io.jenkins.plugins.casc.misc.ConfiguredWithCode; 26 | import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule; 27 | import java.io.IOException; 28 | import java.nio.charset.StandardCharsets; 29 | import java.util.List; 30 | import org.apache.commons.io.IOUtils; 31 | import org.junit.Rule; 32 | import org.junit.Test; 33 | 34 | /** Tests that the credentials are correctly processed by the Configuration as Code plugin. */ 35 | public class ConfigurationAsCodeTest { 36 | 37 | @Rule 38 | public JenkinsConfiguredWithCodeRule r = new JenkinsConfiguredWithCodeRule(); 39 | 40 | @Test 41 | @ConfiguredWithCode("json-service-account-config.yml") 42 | public void supportsConfigurationWithJsonServiceAccountConfig() throws IOException { 43 | List credentialsList = 44 | CredentialsProvider.lookupCredentials(GoogleRobotPrivateKeyCredentials.class); 45 | assertNotNull(credentialsList); 46 | assertEquals("No credentials created", 1, credentialsList.size()); 47 | GoogleRobotPrivateKeyCredentials credentials = credentialsList.get(0); 48 | assertNotNull(credentials); 49 | JsonServiceAccountConfig config = (JsonServiceAccountConfig) credentials.getServiceAccountConfig(); 50 | assertNotNull(config); 51 | assertNull(config.getFilename()); 52 | assertNull(config.getJsonKeyFile()); 53 | assertNull(config.getJsonKeyFileUpload()); 54 | assertNull(config.getPrivateKey()); // Because private_key is not valid. 55 | SecretBytes bytes = config.getSecretJsonKey(); 56 | assertEquals("test-account@test-project.iam.gserviceaccount.com", config.getAccountId()); 57 | String actualBytes = new String(bytes.getPlainData(), StandardCharsets.UTF_8); 58 | String expectedBytes = 59 | IOUtils.toString(this.getClass().getResourceAsStream("test-key.json"), StandardCharsets.UTF_8); 60 | assertEquals("Failed to configure secretJsonKey correctly.", expectedBytes, actualBytes); 61 | } 62 | 63 | @Test 64 | @ConfiguredWithCode("p12-service-account-config.yml") 65 | public void supportsConfigurationWithP12ServiceAccountConfig() { 66 | List credentialsList = 67 | CredentialsProvider.lookupCredentials(GoogleRobotPrivateKeyCredentials.class); 68 | assertNotNull(credentialsList); 69 | assertEquals("No credentials created", 1, credentialsList.size()); 70 | GoogleRobotPrivateKeyCredentials credentials = credentialsList.get(0); 71 | assertNotNull(credentials); 72 | P12ServiceAccountConfig config = (P12ServiceAccountConfig) credentials.getServiceAccountConfig(); 73 | assertNotNull(config); 74 | assertNull(config.getFilename()); 75 | assertNull(config.getP12KeyFile()); 76 | assertNull(config.getP12KeyFileUpload()); 77 | // Because the bytes do not form a valid p12 key file. 78 | assertNull(config.getPrivateKey()); 79 | assertEquals("test-account@test-project.iam.gserviceaccount.com", config.getEmailAddress()); 80 | assertEquals(config.getEmailAddress(), config.getAccountId()); 81 | SecretBytes bytes = config.getSecretP12Key(); 82 | String actualBytes = new String(bytes.getPlainData(), StandardCharsets.UTF_8); 83 | String expectedBytes = "test-p12-key"; 84 | assertEquals("Failed to configure secretP12Key correctly", expectedBytes, actualBytes); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/GoogleOAuth2ScopeSpecificationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import static org.hamcrest.Matchers.hasItems; 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertThat; 21 | 22 | import com.cloudbees.plugins.credentials.domains.DomainSpecification.Result; 23 | import com.google.common.collect.ImmutableList; 24 | import java.util.Collection; 25 | import org.junit.Before; 26 | import org.junit.Rule; 27 | import org.junit.Test; 28 | import org.jvnet.hudson.test.JenkinsRule; 29 | import org.jvnet.hudson.test.WithoutJenkins; 30 | import org.mockito.MockitoAnnotations; 31 | 32 | /** Tests for {@link GoogleOAuth2ScopeSpecification}. */ 33 | public class GoogleOAuth2ScopeSpecificationTest { 34 | // Allow for testing using JUnit4, instead of JUnit3. 35 | @Rule 36 | public JenkinsRule jenkins = new JenkinsRule(); 37 | 38 | @Before 39 | public void setUp() throws Exception { 40 | MockitoAnnotations.initMocks(this); 41 | } 42 | 43 | @Test 44 | @WithoutJenkins 45 | public void testBasics() throws Exception { 46 | GoogleOAuth2ScopeSpecification spec = new GoogleOAuth2ScopeSpecification(GOOD_SCOPES); 47 | 48 | assertThat(spec.getSpecifiedScopes(), hasItems(GOOD_SCOPE1, GOOD_SCOPE2)); 49 | } 50 | 51 | @Test 52 | public void testUnknownRequirement() throws Exception { 53 | GoogleOAuth2ScopeSpecification spec = new GoogleOAuth2ScopeSpecification(GOOD_SCOPES); 54 | 55 | OAuth2ScopeRequirement requirement = new OAuth2ScopeRequirement() { 56 | @Override 57 | public Collection getScopes() { 58 | return GOOD_SCOPES; 59 | } 60 | }; 61 | 62 | // Verify that even with the right scopes the type kind excludes 63 | // the specification from matching this requirement 64 | assertEquals(Result.UNKNOWN, spec.test(requirement)); 65 | } 66 | 67 | @Test 68 | public void testKnownRequirements() throws Exception { 69 | GoogleOAuth2ScopeSpecification spec = new GoogleOAuth2ScopeSpecification(GOOD_SCOPES); 70 | 71 | GoogleOAuth2ScopeRequirement goodReq = new GoogleOAuth2ScopeRequirement() { 72 | @Override 73 | public Collection getScopes() { 74 | return GOOD_SCOPES; 75 | } 76 | }; 77 | GoogleOAuth2ScopeRequirement badReq = new GoogleOAuth2ScopeRequirement() { 78 | @Override 79 | public Collection getScopes() { 80 | return BAD_SCOPES; 81 | } 82 | }; 83 | 84 | // Verify that with the right type of requirement that 85 | // good scopes match POSITIVEly and bad scopes match NEGATIVEly 86 | assertEquals(Result.POSITIVE, spec.test(goodReq)); 87 | assertEquals(Result.NEGATIVE, spec.test(badReq)); 88 | } 89 | 90 | private static String GOOD_SCOPE1 = "foo"; 91 | private static String GOOD_SCOPE2 = "baz"; 92 | private static String BAD_SCOPE = "bar"; 93 | private static Collection GOOD_SCOPES = ImmutableList.of(GOOD_SCOPE1, GOOD_SCOPE2); 94 | private static Collection BAD_SCOPES = ImmutableList.of(GOOD_SCOPE1, BAD_SCOPE); 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/GoogleRobotCredentialsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import static org.hamcrest.CoreMatchers.instanceOf; 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertNotEquals; 21 | import static org.junit.Assert.assertNotNull; 22 | import static org.junit.Assert.assertNull; 23 | import static org.junit.Assert.assertSame; 24 | import static org.junit.Assert.assertThat; 25 | import static org.junit.Assert.assertTrue; 26 | 27 | import com.cloudbees.plugins.credentials.CredentialsNameProvider; 28 | import com.cloudbees.plugins.credentials.CredentialsScope; 29 | import com.cloudbees.plugins.credentials.NameWith; 30 | import com.cloudbees.plugins.credentials.SystemCredentialsProvider; 31 | import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; 32 | import com.google.common.collect.ImmutableList; 33 | import com.google.jenkins.plugins.credentials.domains.RequiresDomain; 34 | import hudson.Extension; 35 | import hudson.model.Descriptor; 36 | import hudson.util.ListBoxModel; 37 | import hudson.util.Secret; 38 | import java.security.GeneralSecurityException; 39 | import java.util.Collection; 40 | import org.junit.Before; 41 | import org.junit.Rule; 42 | import org.junit.Test; 43 | import org.jvnet.hudson.test.JenkinsRule; 44 | import org.jvnet.hudson.test.WithoutJenkins; 45 | import org.jvnet.hudson.test.recipes.LocalData; 46 | import org.mockito.MockitoAnnotations; 47 | 48 | /** Tests for {@link GoogleRobotCredentials}. */ 49 | public class GoogleRobotCredentialsTest { 50 | 51 | // Allow for testing using JUnit4, instead of JUnit3. 52 | @Rule 53 | public JenkinsRule jenkins = new JenkinsRule(); 54 | 55 | /** */ 56 | public static class TestRequirement extends TestGoogleOAuth2DomainRequirement { 57 | public TestRequirement() { 58 | super(FAKE_SCOPE); 59 | } 60 | } 61 | 62 | /** */ 63 | public static class NameProvider extends CredentialsNameProvider { 64 | @Override 65 | public String getName(GoogleRobotCredentials credentials) { 66 | return NAME; 67 | } 68 | } 69 | 70 | /** */ 71 | @NameWith(value = NameProvider.class, priority = 100) 72 | @RequiresDomain(value = TestRequirement.class) 73 | public static class FakeGoogleCredentials extends GoogleRobotCredentials { 74 | public FakeGoogleCredentials(String projectId, GoogleCredential credential) { 75 | super(CredentialsScope.GLOBAL, "", projectId, new GoogleRobotCredentialsModule()); 76 | 77 | this.credential = credential; 78 | } 79 | 80 | @Override 81 | public GoogleCredential getGoogleCredential(GoogleOAuth2ScopeRequirement requirement) 82 | throws GeneralSecurityException { 83 | if (credential == null) { 84 | throw new GeneralSecurityException("asdf"); 85 | } 86 | return credential; 87 | } 88 | 89 | private GoogleCredential credential; 90 | 91 | @Override 92 | public String getUsername() { 93 | return USERNAME; 94 | } 95 | 96 | /** */ 97 | @Extension 98 | public static class DescriptorImpl extends AbstractGoogleRobotCredentialsDescriptor { 99 | public DescriptorImpl() { 100 | super(FakeGoogleCredentials.class); 101 | } 102 | 103 | @Override 104 | public String getDisplayName() { 105 | return DISPLAY_NAME; 106 | } 107 | } 108 | } 109 | 110 | private GoogleCredential fakeCredential; 111 | 112 | @Before 113 | public void setUp() throws Exception { 114 | MockitoAnnotations.initMocks(this); 115 | 116 | fakeCredential = new GoogleCredential(); 117 | } 118 | 119 | @Test 120 | @WithoutJenkins 121 | public void testGettersNullId() throws Exception { 122 | FakeGoogleCredentials credentials = new FakeGoogleCredentials(PROJECT_ID, fakeCredential); 123 | 124 | assertEquals(PROJECT_ID, credentials.getProjectId()); 125 | assertNotNull(credentials.getId()); 126 | assertSame(fakeCredential, credentials.getGoogleCredential(null)); 127 | assertTrue(credentials.getDescription().isEmpty()); 128 | } 129 | 130 | @Test 131 | public void testGetDescriptor() throws Exception { 132 | FakeGoogleCredentials credentials = new FakeGoogleCredentials(PROJECT_ID, fakeCredential); 133 | 134 | Descriptor descriptor = credentials.getDescriptor(); 135 | assertThat(descriptor, instanceOf(FakeGoogleCredentials.DescriptorImpl.class)); 136 | assertEquals(DISPLAY_NAME, descriptor.getDisplayName()); 137 | } 138 | 139 | @Test 140 | @WithoutJenkins 141 | public void testGetAccessToken() throws Exception { 142 | FakeGoogleCredentials credentials = new FakeGoogleCredentials(PROJECT_ID, fakeCredential); 143 | 144 | fakeCredential.setAccessToken(ACCESS_TOKEN); 145 | fakeCredential.setExpiresInSeconds(EXPIRATION_SECONDS); 146 | assertEquals(ACCESS_TOKEN, Secret.toString(credentials.getAccessToken(null /* scope requirement */))); 147 | } 148 | 149 | @Test 150 | @WithoutJenkins 151 | public void testGetAccessTokenNoCredential() throws Exception { 152 | FakeGoogleCredentials credentials = new FakeGoogleCredentials(PROJECT_ID, null /* credential */); 153 | 154 | assertNull(credentials.getAccessToken(null)); 155 | } 156 | 157 | @Test 158 | @WithoutJenkins 159 | public void testForRemote() throws Exception { 160 | FakeGoogleCredentials credentials = new FakeGoogleCredentials(PROJECT_ID, fakeCredential); 161 | 162 | fakeCredential.setAccessToken(ACCESS_TOKEN); 163 | fakeCredential.setExpiresInSeconds(EXPIRATION_SECONDS); 164 | 165 | GoogleOAuth2ScopeRequirement requirement = new GoogleOAuth2ScopeRequirement() { 166 | @Override 167 | public Collection getScopes() { 168 | return ImmutableList.of(); 169 | } 170 | }; 171 | GoogleRobotCredentials remotable = credentials.forRemote(requirement); 172 | 173 | assertEquals(USERNAME, remotable.getUsername()); 174 | assertEquals(ACCESS_TOKEN, Secret.toString(remotable.getAccessToken(requirement))); 175 | assertSame(remotable, remotable.forRemote(requirement)); 176 | } 177 | 178 | @Test 179 | public void testListBoxEmpty() throws Exception { 180 | ListBoxModel list = GoogleRobotCredentials.getCredentialsListBox(FakeGoogleCredentials.class); 181 | 182 | assertEquals(0, list.size()); 183 | 184 | FakeGoogleCredentials credentials = new FakeGoogleCredentials(PROJECT_ID, fakeCredential); 185 | SystemCredentialsProvider.getInstance().getCredentials().add(credentials); 186 | 187 | list = GoogleRobotCredentials.getCredentialsListBox(FakeGoogleCredentials.class); 188 | 189 | assertEquals(1, list.size()); 190 | for (ListBoxModel.Option option : list) { 191 | assertEquals(NAME, option.name); 192 | assertEquals(credentials.getId(), option.value); 193 | } 194 | } 195 | 196 | @Test 197 | public void testGetById() throws Exception { 198 | FakeGoogleCredentials credentials = new FakeGoogleCredentials(PROJECT_ID, fakeCredential); 199 | assertNull(GoogleRobotCredentials.getById(credentials.getId())); 200 | 201 | SystemCredentialsProvider.getInstance().getCredentials().add(credentials); 202 | 203 | assertSame(credentials, GoogleRobotCredentials.getById(credentials.getId())); 204 | assertNull(GoogleRobotCredentials.getById("not an id")); 205 | } 206 | 207 | @LocalData 208 | @Test 209 | public void testMigration() { 210 | /* LocalData contains an old credential with no id field and the project id my-google-project. 211 | On deserialization the id should be filled with the project id. 212 | We didn't specify description in credentials.xml and it should be empty 213 | */ 214 | GoogleRobotCredentials credentials = GoogleRobotCredentials.getById(MIGRATION_PROJECT_ID); 215 | assertNotNull(credentials); 216 | assertTrue(credentials.getDescription().isEmpty()); 217 | } 218 | 219 | @Test 220 | public void testMultipleCredentials() { 221 | FakeGoogleCredentials credential1 = new FakeGoogleCredentials(PROJECT_ID, null); 222 | FakeGoogleCredentials credential2 = new FakeGoogleCredentials(PROJECT_ID, null); 223 | assertNotEquals(credential1, credential2); 224 | } 225 | 226 | private static final String NAME = "my credential name"; 227 | private static final String FAKE_SCOPE = "my.fake.scope"; 228 | private static final String DISPLAY_NAME = "blah"; 229 | private static final String PROJECT_ID = "foo.com:bar-baz"; 230 | private static final String MIGRATION_PROJECT_ID = "my-google-project"; 231 | private static final String USERNAME = "mattomata"; 232 | private static final String ACCESS_TOKEN = "ThE.ToKeN"; 233 | private static final long EXPIRATION_SECONDS = 1234; 234 | } 235 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/JsonServiceAccountConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertNull; 20 | import static org.mockito.Mockito.when; 21 | 22 | import com.cloudbees.plugins.credentials.SecretBytes; 23 | import java.io.ByteArrayInputStream; 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.File; 26 | import java.io.FileInputStream; 27 | import java.security.PrivateKey; 28 | import org.apache.commons.fileupload.FileItem; 29 | import org.apache.commons.io.FileUtils; 30 | import org.junit.Before; 31 | import org.junit.BeforeClass; 32 | import org.junit.Rule; 33 | import org.junit.Test; 34 | import org.jvnet.hudson.test.JenkinsRule; 35 | import org.mockito.Mock; 36 | import org.mockito.MockitoAnnotations; 37 | 38 | /** Tests for {@link JsonServiceAccountConfig}. */ 39 | public class JsonServiceAccountConfigTest { 40 | private static final String SERVICE_ACCOUNT_EMAIL_ADDRESS = "service@account.com"; 41 | private static PrivateKey privateKey; 42 | private static String jsonKeyPath; 43 | 44 | @Rule 45 | public JenkinsRule jenkinsRule = new JenkinsRule(); 46 | 47 | @Mock 48 | private FileItem mockFileItem; 49 | 50 | @BeforeClass 51 | public static void preparePrivateKey() throws Exception { 52 | privateKey = JsonServiceAccountConfigTestUtil.generatePrivateKey(); 53 | jsonKeyPath = JsonServiceAccountConfigTestUtil.createTempJsonKeyFile(SERVICE_ACCOUNT_EMAIL_ADDRESS, privateKey); 54 | } 55 | 56 | @Before 57 | public void setUp() { 58 | MockitoAnnotations.initMocks(this); 59 | } 60 | 61 | @Test 62 | public void testCreateJsonKeyTypeWithNewJsonKeyFile() throws Exception { 63 | when(mockFileItem.getSize()).thenReturn(1L); 64 | when(mockFileItem.getInputStream()).thenReturn(new FileInputStream(jsonKeyPath)); 65 | when(mockFileItem.getName()).thenReturn(jsonKeyPath); 66 | when(mockFileItem.get()).thenReturn(FileUtils.readFileToByteArray(new File(jsonKeyPath))); 67 | JsonServiceAccountConfig jsonKeyType = new JsonServiceAccountConfig(); 68 | jsonKeyType.setJsonKeyFileUpload(mockFileItem); 69 | 70 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, jsonKeyType.getAccountId()); 71 | assertEquals(privateKey, jsonKeyType.getPrivateKey()); 72 | } 73 | 74 | @Test 75 | public void testCreateJsonKeyTypeWithNullParameters() { 76 | JsonServiceAccountConfig jsonServiceAccountConfig = new JsonServiceAccountConfig(); 77 | 78 | assertNull(jsonServiceAccountConfig.getAccountId()); 79 | assertNull(jsonServiceAccountConfig.getPrivateKey()); 80 | } 81 | 82 | @Test 83 | public void testCreateJsonKeyTypeWithEmptyJsonKeyFile() throws Exception { 84 | when(mockFileItem.getSize()).thenReturn(0L); 85 | JsonServiceAccountConfig jsonKeyType = new JsonServiceAccountConfig(); 86 | jsonKeyType.setJsonKeyFileUpload(mockFileItem); 87 | 88 | assertNull(jsonKeyType.getJsonKeyFile()); 89 | assertNull(jsonKeyType.getAccountId()); 90 | assertNull(jsonKeyType.getPrivateKey()); 91 | } 92 | 93 | @Test 94 | public void testCreateJsonKeyTypeWithInvalidJsonKeyFile() throws Exception { 95 | byte[] bytes = "invalidJsonKeyFile".getBytes(); 96 | when(mockFileItem.getSize()).thenReturn((long) bytes.length); 97 | when(mockFileItem.getInputStream()).thenReturn(new ByteArrayInputStream(bytes)); 98 | JsonServiceAccountConfig jsonServiceAccountConfig = new JsonServiceAccountConfig(); 99 | jsonServiceAccountConfig.setJsonKeyFileUpload(mockFileItem); 100 | 101 | assertNull(jsonServiceAccountConfig.getAccountId()); 102 | assertNull(jsonServiceAccountConfig.getPrivateKey()); 103 | } 104 | 105 | @Test 106 | public void testCreateJsonKeyTypeWithPrevJsonKeyFileForCompatibility() { 107 | JsonServiceAccountConfig jsonServiceAccountConfig = new JsonServiceAccountConfig(null, jsonKeyPath); 108 | 109 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, jsonServiceAccountConfig.getAccountId()); 110 | assertEquals(privateKey, jsonServiceAccountConfig.getPrivateKey()); 111 | } 112 | 113 | @Test 114 | public void testCreateJsonKeyTypeWithPrevJsonKeyFile() throws Exception { 115 | SecretBytes prev = SecretBytes.fromBytes(FileUtils.readFileToByteArray(new File(jsonKeyPath))); 116 | JsonServiceAccountConfig jsonServiceAccountConfig = new JsonServiceAccountConfig(); 117 | jsonServiceAccountConfig.setFilename(jsonKeyPath); 118 | jsonServiceAccountConfig.setSecretJsonKey(prev); 119 | 120 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, jsonServiceAccountConfig.getAccountId()); 121 | assertEquals(privateKey, jsonServiceAccountConfig.getPrivateKey()); 122 | } 123 | 124 | @Test 125 | public void testCreateJsonKeyTypeWithEmptyPrevJsonKeyFile() { 126 | SecretBytes prev = SecretBytes.fromString(""); 127 | JsonServiceAccountConfig jsonServiceAccountConfig = new JsonServiceAccountConfig(); 128 | jsonServiceAccountConfig.setFilename(""); 129 | jsonServiceAccountConfig.setSecretJsonKey(prev); 130 | 131 | assertNull(jsonServiceAccountConfig.getAccountId()); 132 | assertNull(jsonServiceAccountConfig.getPrivateKey()); 133 | } 134 | 135 | @Test 136 | public void testCreateJsonKeyTypeWithInvalidPrevJsonKeyFile() { 137 | JsonServiceAccountConfig jsonServiceAccountConfig = 138 | new JsonServiceAccountConfig(null, "invalidPrevJsonKeyFile.json"); 139 | 140 | assertNull(jsonServiceAccountConfig.getAccountId()); 141 | assertNull(jsonServiceAccountConfig.getPrivateKey()); 142 | } 143 | 144 | @Test 145 | public void testSerialization() throws Exception { 146 | when(mockFileItem.getSize()).thenReturn(1L); 147 | when(mockFileItem.getName()).thenReturn(jsonKeyPath); 148 | when(mockFileItem.getInputStream()).thenReturn(new FileInputStream(jsonKeyPath)); 149 | when(mockFileItem.get()).thenReturn(FileUtils.readFileToByteArray(new File(jsonKeyPath))); 150 | JsonServiceAccountConfig jsonServiceAccountConfig = new JsonServiceAccountConfig(); 151 | jsonServiceAccountConfig.setJsonKeyFileUpload(mockFileItem); 152 | 153 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 154 | SerializationUtil.serialize(jsonServiceAccountConfig, out); 155 | ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); 156 | JsonServiceAccountConfig deserializedJsonKeyType = 157 | SerializationUtil.deserialize(JsonServiceAccountConfig.class, in); 158 | 159 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, deserializedJsonKeyType.getAccountId()); 160 | assertEquals(privateKey, deserializedJsonKeyType.getPrivateKey()); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/JsonServiceAccountConfigTestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.google.api.client.json.JsonGenerator; 19 | import com.google.api.client.json.jackson2.JacksonFactory; 20 | import java.io.File; 21 | import java.io.FileOutputStream; 22 | import java.io.IOException; 23 | import java.io.StringWriter; 24 | import java.nio.charset.Charset; 25 | import java.nio.file.Files; 26 | import java.security.KeyPair; 27 | import java.security.KeyPairGenerator; 28 | import java.security.NoSuchAlgorithmException; 29 | import java.security.NoSuchProviderException; 30 | import java.security.PrivateKey; 31 | import java.security.Security; 32 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 33 | import org.bouncycastle.openssl.PEMWriter; 34 | 35 | /** Util class for {@link JsonServiceAccountConfigTest}. */ 36 | public class JsonServiceAccountConfigTestUtil { 37 | private static File tempFolder; 38 | 39 | public static PrivateKey generatePrivateKey() throws NoSuchProviderException, NoSuchAlgorithmException { 40 | Security.addProvider(new BouncyCastleProvider()); 41 | final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); 42 | keyPairGenerator.initialize(1024); 43 | final KeyPair keyPair = keyPairGenerator.generateKeyPair(); 44 | return keyPair.getPrivate(); 45 | } 46 | 47 | public static String createTempJsonKeyFile(String clientEmail, PrivateKey privateKey) throws IOException { 48 | final File tempJsonKey = File.createTempFile("temp-key", ".json", getTempFolder()); 49 | JsonGenerator jsonGenerator = null; 50 | try { 51 | jsonGenerator = new JacksonFactory() 52 | .createJsonGenerator(new FileOutputStream(tempJsonKey), Charset.forName("UTF-8")); 53 | jsonGenerator.enablePrettyPrint(); 54 | jsonGenerator.serialize(createJsonKey(clientEmail, privateKey)); 55 | } finally { 56 | if (jsonGenerator != null) { 57 | jsonGenerator.close(); 58 | } 59 | } 60 | return tempJsonKey.getAbsolutePath(); 61 | } 62 | 63 | private static File getTempFolder() throws IOException { 64 | if (tempFolder == null) { 65 | tempFolder = Files.createTempDirectory("temp" + Long.toString(System.nanoTime())) 66 | .toFile(); 67 | tempFolder.deleteOnExit(); 68 | } 69 | return tempFolder; 70 | } 71 | 72 | private static JsonKey createJsonKey(String clientEmail, PrivateKey privateKey) throws IOException { 73 | final JsonKey jsonKey = new JsonKey(); 74 | jsonKey.setClientEmail(clientEmail); 75 | jsonKey.setPrivateKey(getInPemFormat(privateKey)); 76 | return jsonKey; 77 | } 78 | 79 | private static String getInPemFormat(PrivateKey privateKey) throws IOException { 80 | final StringWriter stringWriter = new StringWriter(); 81 | final PEMWriter pemWriter = new PEMWriter(stringWriter); 82 | pemWriter.writeObject(privateKey); 83 | pemWriter.flush(); 84 | pemWriter.close(); 85 | return stringWriter.toString(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/LegacyJsonServiceAccountConfigUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.google.api.client.json.JsonGenerator; 19 | import com.google.api.client.json.jackson2.JacksonFactory; 20 | import java.io.File; 21 | import java.io.FileOutputStream; 22 | import java.io.IOException; 23 | import java.nio.charset.Charset; 24 | import java.nio.file.Files; 25 | 26 | /** 27 | * Util class for {@link com.google.jenkins.plugins.credentials.oauth 28 | * .GoogleRobotPrivateKeyCredentials}. 29 | */ 30 | public class LegacyJsonServiceAccountConfigUtil { 31 | private static File tempFolder; 32 | 33 | public static String createTempLegacyJsonKeyFile(String clientEmail) throws IOException { 34 | final File tempLegacyJsonKey = File.createTempFile("temp-legacykey", ".json", getTempFolder()); 35 | final JsonGenerator jsonGenerator = new JacksonFactory() 36 | .createJsonGenerator(new FileOutputStream(tempLegacyJsonKey), Charset.forName("UTF-8")); 37 | jsonGenerator.enablePrettyPrint(); 38 | jsonGenerator.serialize(createLegacyJsonKey(clientEmail)); 39 | jsonGenerator.close(); 40 | return tempLegacyJsonKey.getAbsolutePath(); 41 | } 42 | 43 | public static String createTempLegacyJsonKeyFileWithMissingWebObject() throws IOException { 44 | final File tempLegacyJsonKey = File.createTempFile("temp-legacykey", ".json", getTempFolder()); 45 | final JsonGenerator jsonGenerator = new JacksonFactory() 46 | .createJsonGenerator(new FileOutputStream(tempLegacyJsonKey), Charset.forName("UTF-8")); 47 | jsonGenerator.enablePrettyPrint(); 48 | jsonGenerator.serialize(createLegacyJsonKeyWithMissingWebObject()); 49 | jsonGenerator.close(); 50 | return tempLegacyJsonKey.getAbsolutePath(); 51 | } 52 | 53 | public static String createTempLegacyJsonKeyFileWithMissingClientEmail() throws IOException { 54 | final File tempLegacyJsonKey = File.createTempFile("temp-legacykey", ".json", getTempFolder()); 55 | JsonGenerator jsonGenerator = null; 56 | try { 57 | jsonGenerator = new JacksonFactory() 58 | .createJsonGenerator(new FileOutputStream(tempLegacyJsonKey), Charset.forName("UTF-8")); 59 | jsonGenerator.enablePrettyPrint(); 60 | jsonGenerator.serialize(createLegacyJsonKeyWithMissingClientEmail()); 61 | } finally { 62 | if (jsonGenerator != null) { 63 | jsonGenerator.close(); 64 | } 65 | } 66 | return tempLegacyJsonKey.getAbsolutePath(); 67 | } 68 | 69 | public static String createTempInvalidLegacyJsonKeyFile() throws IOException { 70 | final File tempLegacyJsonKey = File.createTempFile("temp-legacykey", ".json", getTempFolder()); 71 | FileOutputStream out = null; 72 | try { 73 | out = new FileOutputStream(tempLegacyJsonKey); 74 | out.write("InvalidLegacyJsonKeyFile".getBytes()); 75 | out.flush(); 76 | } finally { 77 | if (out != null) { 78 | out.close(); 79 | } 80 | } 81 | return tempLegacyJsonKey.getAbsolutePath(); 82 | } 83 | 84 | private static File getTempFolder() throws IOException { 85 | if (tempFolder == null) { 86 | tempFolder = Files.createTempDirectory("temp" + Long.toString(System.nanoTime())) 87 | .toFile(); 88 | tempFolder.deleteOnExit(); 89 | } 90 | return tempFolder; 91 | } 92 | 93 | @SuppressWarnings("deprecation") 94 | private static LegacyJsonKey createLegacyJsonKey(String clientEmail) throws IOException { 95 | final LegacyJsonKey legacyJsonKey = new LegacyJsonKey(); 96 | LegacyJsonKey.Details web = new LegacyJsonKey.Details(); 97 | web.setClientEmail(clientEmail); 98 | legacyJsonKey.setWeb(web); 99 | return legacyJsonKey; 100 | } 101 | 102 | @SuppressWarnings("deprecation") 103 | private static LegacyJsonKey createLegacyJsonKeyWithMissingWebObject() throws IOException { 104 | return new LegacyJsonKey(); 105 | } 106 | 107 | @SuppressWarnings("deprecation") 108 | private static LegacyJsonKey createLegacyJsonKeyWithMissingClientEmail() throws IOException { 109 | final LegacyJsonKey legacyJsonKey = new LegacyJsonKey(); 110 | legacyJsonKey.setWeb(new LegacyJsonKey.Details()); 111 | return legacyJsonKey; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/P12ServiceAccountConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertNull; 20 | import static org.mockito.Mockito.when; 21 | 22 | import com.cloudbees.plugins.credentials.SecretBytes; 23 | import java.io.ByteArrayInputStream; 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.File; 26 | import java.security.KeyPair; 27 | import org.apache.commons.fileupload.FileItem; 28 | import org.apache.commons.io.FileUtils; 29 | import org.junit.Before; 30 | import org.junit.BeforeClass; 31 | import org.junit.Rule; 32 | import org.junit.Test; 33 | import org.jvnet.hudson.test.JenkinsRule; 34 | import org.jvnet.hudson.test.WithoutJenkins; 35 | import org.mockito.Mock; 36 | import org.mockito.MockitoAnnotations; 37 | 38 | /** Tests for {@link P12ServiceAccountConfig}. */ 39 | public class P12ServiceAccountConfigTest { 40 | private static final String SERVICE_ACCOUNT_EMAIL_ADDRESS = "service@account.com"; 41 | private static KeyPair keyPair; 42 | private static String p12KeyPath; 43 | 44 | @Rule 45 | public JenkinsRule jenkinsRule = new JenkinsRule(); 46 | 47 | @Mock 48 | private FileItem mockFileItem; 49 | 50 | @BeforeClass 51 | public static void preparePrivateKey() throws Exception { 52 | keyPair = P12ServiceAccountConfigTestUtil.generateKeyPair(); 53 | p12KeyPath = P12ServiceAccountConfigTestUtil.createTempP12KeyFile(keyPair); 54 | } 55 | 56 | @Before 57 | public void setUp() throws Exception { 58 | MockitoAnnotations.initMocks(this); 59 | } 60 | 61 | @Test 62 | public void testCreateWithNewP12KeyFile() throws Exception { 63 | when(mockFileItem.getSize()).thenReturn(1L); 64 | when(mockFileItem.getName()).thenReturn(p12KeyPath); 65 | when(mockFileItem.get()).thenReturn(FileUtils.readFileToByteArray(new File(p12KeyPath))); 66 | P12ServiceAccountConfig p12ServiceAccountConfig = new P12ServiceAccountConfig(SERVICE_ACCOUNT_EMAIL_ADDRESS); 67 | p12ServiceAccountConfig.setP12KeyFileUpload(mockFileItem); 68 | 69 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, p12ServiceAccountConfig.getAccountId()); 70 | assertEquals(keyPair.getPrivate(), p12ServiceAccountConfig.getPrivateKey()); 71 | } 72 | 73 | @Test 74 | public void testCreateWithNullAccountId() throws Exception { 75 | SecretBytes prev = SecretBytes.fromBytes(FileUtils.readFileToByteArray(new File(p12KeyPath))); 76 | P12ServiceAccountConfig p12ServiceAccountConfig = new P12ServiceAccountConfig(null); 77 | p12ServiceAccountConfig.setFilename(p12KeyPath); 78 | p12ServiceAccountConfig.setSecretP12Key(prev); 79 | 80 | assertNull(p12ServiceAccountConfig.getAccountId()); 81 | assertEquals(keyPair.getPrivate(), p12ServiceAccountConfig.getPrivateKey()); 82 | } 83 | 84 | @Test 85 | @WithoutJenkins 86 | public void testCreateWithNullP12KeyFile() { 87 | P12ServiceAccountConfig p12ServiceAccountConfig = new P12ServiceAccountConfig(SERVICE_ACCOUNT_EMAIL_ADDRESS); 88 | 89 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, p12ServiceAccountConfig.getAccountId()); 90 | assertNull(p12ServiceAccountConfig.getPrivateKey()); 91 | } 92 | 93 | @Test 94 | @WithoutJenkins 95 | public void testCreateWithEmptyP12KeyFile() throws Exception { 96 | when(mockFileItem.getSize()).thenReturn(0L); 97 | P12ServiceAccountConfig p12ServiceAccountConfig = new P12ServiceAccountConfig(SERVICE_ACCOUNT_EMAIL_ADDRESS); 98 | p12ServiceAccountConfig.setP12KeyFileUpload(mockFileItem); 99 | 100 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, p12ServiceAccountConfig.getAccountId()); 101 | assertNull(p12ServiceAccountConfig.getPrivateKey()); 102 | } 103 | 104 | @Test 105 | public void testCreateWithInvalidP12KeyFile() { 106 | byte[] bytes = "invalidP12KeyFile".getBytes(); 107 | when(mockFileItem.getSize()).thenReturn((long) bytes.length); 108 | when(mockFileItem.getName()).thenReturn("invalidP12KeyFile"); 109 | when(mockFileItem.get()).thenReturn(bytes); 110 | P12ServiceAccountConfig p12ServiceAccountConfig = new P12ServiceAccountConfig(SERVICE_ACCOUNT_EMAIL_ADDRESS); 111 | p12ServiceAccountConfig.setP12KeyFileUpload(mockFileItem); 112 | 113 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, p12ServiceAccountConfig.getAccountId()); 114 | assertNull(p12ServiceAccountConfig.getPrivateKey()); 115 | } 116 | 117 | @Test 118 | public void testCreateWithPrevP12KeyFileForCompatibility() { 119 | P12ServiceAccountConfig p12ServiceAccountConfig = 120 | new P12ServiceAccountConfig(SERVICE_ACCOUNT_EMAIL_ADDRESS, null, p12KeyPath); 121 | 122 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, p12ServiceAccountConfig.getAccountId()); 123 | assertEquals(keyPair.getPrivate(), p12ServiceAccountConfig.getPrivateKey()); 124 | } 125 | 126 | @Test 127 | public void testCreateWithPrevP12KeyFile() throws Exception { 128 | SecretBytes prev = SecretBytes.fromBytes(FileUtils.readFileToByteArray(new File(p12KeyPath))); 129 | P12ServiceAccountConfig p12ServiceAccountConfig = new P12ServiceAccountConfig(SERVICE_ACCOUNT_EMAIL_ADDRESS); 130 | p12ServiceAccountConfig.setFilename(p12KeyPath); 131 | p12ServiceAccountConfig.setSecretP12Key(prev); 132 | 133 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, p12ServiceAccountConfig.getAccountId()); 134 | assertEquals(keyPair.getPrivate(), p12ServiceAccountConfig.getPrivateKey()); 135 | } 136 | 137 | @Test 138 | public void testCreateWithEmptyPrevP12KeyFile() { 139 | SecretBytes prev = SecretBytes.fromString(""); 140 | P12ServiceAccountConfig p12ServiceAccountConfig = new P12ServiceAccountConfig(SERVICE_ACCOUNT_EMAIL_ADDRESS); 141 | p12ServiceAccountConfig.setFilename(""); 142 | p12ServiceAccountConfig.setSecretP12Key(prev); 143 | 144 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, p12ServiceAccountConfig.getAccountId()); 145 | assertNull(p12ServiceAccountConfig.getPrivateKey()); 146 | } 147 | 148 | @Test 149 | @WithoutJenkins 150 | public void testCreateWithInvalidPrevP12KeyFile() { 151 | P12ServiceAccountConfig p12ServiceAccountConfig = new P12ServiceAccountConfig(SERVICE_ACCOUNT_EMAIL_ADDRESS); 152 | p12ServiceAccountConfig.setFilename("invalidPrevP12KeyFile.p12"); 153 | 154 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, p12ServiceAccountConfig.getAccountId()); 155 | assertNull(p12ServiceAccountConfig.getPrivateKey()); 156 | } 157 | 158 | @Test 159 | public void testSerialization() throws Exception { 160 | when(mockFileItem.getSize()).thenReturn(1L); 161 | when(mockFileItem.getName()).thenReturn(p12KeyPath); 162 | when(mockFileItem.get()).thenReturn(FileUtils.readFileToByteArray(new File(p12KeyPath))); 163 | P12ServiceAccountConfig p12ServiceAccountConfig = new P12ServiceAccountConfig(SERVICE_ACCOUNT_EMAIL_ADDRESS); 164 | p12ServiceAccountConfig.setP12KeyFileUpload(mockFileItem); 165 | 166 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 167 | SerializationUtil.serialize(p12ServiceAccountConfig, out); 168 | ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); 169 | P12ServiceAccountConfig deserializedP12KeyType = 170 | SerializationUtil.deserialize(P12ServiceAccountConfig.class, in); 171 | 172 | assertEquals(SERVICE_ACCOUNT_EMAIL_ADDRESS, deserializedP12KeyType.getAccountId()); 173 | assertEquals(keyPair.getPrivate(), deserializedP12KeyType.getPrivateKey()); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/P12ServiceAccountConfigTestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import java.io.File; 19 | import java.io.FileOutputStream; 20 | import java.io.IOException; 21 | import java.math.BigInteger; 22 | import java.nio.file.Files; 23 | import java.security.KeyPair; 24 | import java.security.KeyPairGenerator; 25 | import java.security.KeyStore; 26 | import java.security.KeyStoreException; 27 | import java.security.NoSuchAlgorithmException; 28 | import java.security.NoSuchProviderException; 29 | import java.security.Security; 30 | import java.security.cert.Certificate; 31 | import java.security.cert.CertificateException; 32 | import java.security.cert.X509Certificate; 33 | import java.util.Calendar; 34 | import org.apache.commons.io.IOUtils; 35 | import org.bouncycastle.asn1.x500.X500Name; 36 | import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; 37 | import org.bouncycastle.cert.X509CertificateHolder; 38 | import org.bouncycastle.cert.X509v3CertificateBuilder; 39 | import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; 40 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 41 | import org.bouncycastle.operator.ContentSigner; 42 | import org.bouncycastle.operator.OperatorCreationException; 43 | import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 44 | 45 | /** Util class for {@link P12ServiceAccountConfigTest}. */ 46 | public class P12ServiceAccountConfigTestUtil { 47 | private static final String DEFAULT_P12_SECRET = "notasecret"; 48 | private static final String DEFAULT_P12_ALIAS = "privatekey"; 49 | private static File tempFolder; 50 | 51 | public static KeyPair generateKeyPair() throws NoSuchProviderException, NoSuchAlgorithmException { 52 | Security.addProvider(new BouncyCastleProvider()); 53 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); 54 | keyPairGenerator.initialize(1024); 55 | return keyPairGenerator.generateKeyPair(); 56 | } 57 | 58 | public static String createTempP12KeyFile(KeyPair keyPair) 59 | throws IOException, OperatorCreationException, CertificateException, NoSuchAlgorithmException, 60 | KeyStoreException, NoSuchProviderException { 61 | File tempP12Key = File.createTempFile("temp-key", ".p12", getTempFolder()); 62 | writeKeyToFile(keyPair, tempP12Key); 63 | return tempP12Key.getAbsolutePath(); 64 | } 65 | 66 | private static File getTempFolder() throws IOException { 67 | if (tempFolder == null) { 68 | tempFolder = Files.createTempDirectory("temp" + Long.toString(System.nanoTime())) 69 | .toFile(); 70 | tempFolder.deleteOnExit(); 71 | } 72 | return tempFolder; 73 | } 74 | 75 | private static void writeKeyToFile(KeyPair keyPair, File tempP12Key) 76 | throws IOException, OperatorCreationException, CertificateException, NoSuchAlgorithmException, 77 | KeyStoreException, NoSuchProviderException { 78 | FileOutputStream out = null; 79 | try { 80 | out = new FileOutputStream(tempP12Key); 81 | KeyStore keyStore = createKeyStore(keyPair); 82 | keyStore.store(out, DEFAULT_P12_SECRET.toCharArray()); 83 | } finally { 84 | IOUtils.closeQuietly(out); 85 | } 86 | } 87 | 88 | private static KeyStore createKeyStore(KeyPair keyPair) 89 | throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, 90 | OperatorCreationException, NoSuchProviderException { 91 | KeyStore keyStore = KeyStore.getInstance("PKCS12"); 92 | keyStore.load(null, null); 93 | keyStore.setKeyEntry( 94 | DEFAULT_P12_ALIAS, keyPair.getPrivate(), DEFAULT_P12_SECRET.toCharArray(), new Certificate[] { 95 | generateCertificate(keyPair) 96 | }); 97 | return keyStore; 98 | } 99 | 100 | private static X509Certificate generateCertificate(KeyPair keyPair) 101 | throws OperatorCreationException, CertificateException { 102 | Calendar endCalendar = Calendar.getInstance(); 103 | endCalendar.add(Calendar.YEAR, 10); 104 | X509v3CertificateBuilder x509v3CertificateBuilder = new X509v3CertificateBuilder( 105 | new X500Name("CN=localhost"), 106 | BigInteger.valueOf(1), 107 | Calendar.getInstance().getTime(), 108 | endCalendar.getTime(), 109 | new X500Name("CN=localhost"), 110 | SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded())); 111 | ContentSigner contentSigner = new JcaContentSignerBuilder("SHA1withRSA").build(keyPair.getPrivate()); 112 | X509CertificateHolder x509CertificateHolder = x509v3CertificateBuilder.build(contentSigner); 113 | return new JcaX509CertificateConverter().setProvider("BC").getCertificate(x509CertificateHolder); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/RemotableGoogleCredentialsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import static org.hamcrest.Matchers.closeTo; 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertThat; 21 | import static org.mockito.Mockito.when; 22 | 23 | import com.cloudbees.plugins.credentials.CredentialsNameProvider; 24 | import com.cloudbees.plugins.credentials.CredentialsScope; 25 | import com.google.api.client.auth.oauth2.Credential; 26 | import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; 27 | import java.security.GeneralSecurityException; 28 | import org.joda.time.DateTime; 29 | import org.joda.time.DateTimeUtils; 30 | import org.junit.Before; 31 | import org.junit.Test; 32 | import org.mockito.Mock; 33 | import org.mockito.MockitoAnnotations; 34 | 35 | /** Tests for {@link RemotableGoogleCredentials}. */ 36 | public class RemotableGoogleCredentialsTest { 37 | 38 | private GoogleCredential fakeCredential; 39 | 40 | @Mock 41 | private GoogleRobotCredentials mockCredentials; 42 | 43 | private TestGoogleOAuth2DomainRequirement testConsumer; 44 | 45 | private GoogleRobotCredentialsModule module; 46 | 47 | @Before 48 | public void setUp() throws Exception { 49 | MockitoAnnotations.initMocks(this); 50 | 51 | // Freeze time 52 | DateTime now = new DateTime(); 53 | DateTimeUtils.setCurrentMillisFixed(now.getMillis()); 54 | 55 | this.module = new GoogleRobotCredentialsModule(); 56 | 57 | this.testConsumer = new TestGoogleOAuth2DomainRequirement(THE_SCOPE); 58 | this.fakeCredential = new GoogleCredential(); 59 | 60 | when(mockCredentials.getProjectId()).thenReturn(PROJECT_ID); 61 | when(mockCredentials.getGoogleCredential(testConsumer)).thenReturn(fakeCredential); 62 | when(mockCredentials.getUsername()).thenReturn(USERNAME); 63 | } 64 | 65 | @Test 66 | public void testUsername() throws Exception { 67 | fakeCredential.setAccessToken(ACCESS_TOKEN); 68 | fakeCredential.setExpiresInSeconds(EXPIRATION_SECONDS); 69 | 70 | GoogleRobotCredentials credentials = new RemotableGoogleCredentials(mockCredentials, testConsumer, module); 71 | Credential credential = credentials.getGoogleCredential(testConsumer); 72 | 73 | assertEquals(USERNAME, credentials.getUsername()); 74 | assertEquals(CredentialsScope.GLOBAL, credentials.getScope()); 75 | } 76 | 77 | @Test(expected = GeneralSecurityException.class) 78 | public void testNullExpirationBadRefresh() throws Exception { 79 | new RemotableGoogleCredentials(mockCredentials, testConsumer, module); 80 | } 81 | 82 | @Test(expected = GeneralSecurityException.class) 83 | public void testImminentExpirationBadRefresh() throws Exception { 84 | fakeCredential.setExpiresInSeconds(IMMINENT_EXPIRATION_SECONDS); 85 | new RemotableGoogleCredentials(mockCredentials, testConsumer, module); 86 | } 87 | 88 | @Test 89 | public void testReasonableExpiration() throws Exception { 90 | fakeCredential.setAccessToken(ACCESS_TOKEN); 91 | fakeCredential.setExpiresInSeconds(EXPIRATION_SECONDS); 92 | 93 | GoogleRobotCredentials credentials = new RemotableGoogleCredentials(mockCredentials, testConsumer, module); 94 | Credential credential = credentials.getGoogleCredential(testConsumer); 95 | 96 | assertEquals(ACCESS_TOKEN, credential.getAccessToken()); 97 | assertThat(credential.getExpiresInSeconds().doubleValue(), closeTo(EXPIRATION_SECONDS, 2)); 98 | } 99 | 100 | public void testName() throws Exception { 101 | fakeCredential.setAccessToken(ACCESS_TOKEN); 102 | fakeCredential.setExpiresInSeconds(EXPIRATION_SECONDS); 103 | 104 | GoogleRobotCredentials credentials = new RemotableGoogleCredentials(mockCredentials, testConsumer, module); 105 | 106 | assertEquals("RemotableGoogleCredentials", CredentialsNameProvider.name(credentials)); 107 | } 108 | 109 | @Test(expected = UnsupportedOperationException.class) 110 | public void testUnsupportedDescriptor() throws Exception { 111 | fakeCredential.setAccessToken(ACCESS_TOKEN); 112 | fakeCredential.setExpiresInSeconds(EXPIRATION_SECONDS); 113 | 114 | GoogleRobotCredentials credentials = new RemotableGoogleCredentials(mockCredentials, testConsumer, module); 115 | 116 | credentials.getDescriptor(); 117 | } 118 | 119 | private static final long ERROR = 1; // 1 second error 120 | private static final long IMMINENT_EXPIRATION_SECONDS = 60; 121 | private static final long EXPIRATION_SECONDS = 1234; 122 | private static final String USERNAME = "theUserName"; 123 | private static final String PROJECT_ID = "foo.com:bar-baz"; 124 | private static final String THE_SCOPE = "my.scope"; 125 | private static final String BAD_SCOPE = "NOT.my.scope"; 126 | private static final String ACCESS_TOKEN = "ThE.ToKeN"; 127 | } 128 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/SerializationUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import java.io.*; 19 | 20 | /** Helper class for Serialization */ 21 | public class SerializationUtil { 22 | public static void serialize(Object object, OutputStream out) throws IOException { 23 | ObjectOutputStream objectOut = null; 24 | try { 25 | objectOut = new ObjectOutputStream(out); 26 | objectOut.writeObject(object); 27 | } finally { 28 | if (objectOut != null) { 29 | try { 30 | objectOut.close(); 31 | } catch (IOException ignored) { 32 | } 33 | } 34 | } 35 | } 36 | 37 | public static T deserialize(Class clazz, InputStream in) 38 | throws IOException, ClassNotFoundException, ClassCastException { 39 | ObjectInputStream objectIn = null; 40 | try { 41 | objectIn = new ObjectInputStream(in); 42 | return clazz.cast(objectIn.readObject()); 43 | } finally { 44 | if (objectIn != null) { 45 | try { 46 | objectIn.close(); 47 | } catch (IOException ignored) { 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/TestGoogleOAuth2DomainRequirement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import java.util.Collection; 19 | import java.util.Collections; 20 | 21 | /** This is a trivial implementation of a {@link GoogleOAuth2ScopeRequirement}. */ 22 | public class TestGoogleOAuth2DomainRequirement extends GoogleOAuth2ScopeRequirement { 23 | private static final long serialVersionUID = 2234181311205118742L; 24 | 25 | public TestGoogleOAuth2DomainRequirement(String scope) { 26 | this.scope = scope; 27 | } 28 | 29 | @Override 30 | public Collection getScopes() { 31 | return Collections.singletonList(scope); 32 | } 33 | 34 | private final String scope; 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/credentials/oauth/TestRobotBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.credentials.oauth; 17 | 18 | import com.google.jenkins.plugins.credentials.domains.RequiresDomain; 19 | import hudson.model.Descriptor; 20 | import hudson.tasks.Builder; 21 | 22 | /** 23 | * This is a trivial implementation of a {@link Builder} that consumes {@link 24 | * GoogleRobotCredentials}. 25 | */ 26 | @RequiresDomain(value = TestGoogleOAuth2DomainRequirement.class) 27 | public class TestRobotBuilder extends Builder { 28 | public TestRobotBuilder() {} 29 | 30 | @Override 31 | public DescriptorImpl getDescriptor() { 32 | return DESCRIPTOR; 33 | } 34 | 35 | /** Descriptor for our trivial builder */ 36 | public static final class DescriptorImpl extends Descriptor { 37 | @Override 38 | public String getDisplayName() { 39 | return "Test Robot Builder"; 40 | } 41 | } 42 | 43 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/util/ExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_FORBIDDEN; 19 | import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_NOT_FOUND; 20 | import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_SERVER_ERROR; 21 | import static org.junit.Assert.assertNotNull; 22 | import static org.mockito.Mockito.when; 23 | 24 | import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest; 25 | import com.google.api.client.http.HttpResponseException; 26 | import java.net.SocketTimeoutException; 27 | import org.junit.Before; 28 | import org.junit.Test; 29 | import org.mockito.Mock; 30 | import org.mockito.MockitoAnnotations; 31 | 32 | /** Tests for {@link Executor}. */ 33 | public class ExecutorTest { 34 | 35 | private HttpResponseException notFoundJsonException; 36 | private HttpResponseException conflictJsonException; 37 | private HttpResponseException forbiddenJsonException; 38 | private HttpResponseException errorJsonException; 39 | private SocketTimeoutException timeoutException; 40 | 41 | @Mock 42 | private com.google.api.client.http.HttpHeaders headers; 43 | 44 | @Mock 45 | private AbstractGoogleJsonClientRequest mockRequest; 46 | 47 | private Executor underTest; 48 | 49 | @Before 50 | public void setUp() throws Exception { 51 | MockitoAnnotations.initMocks(this); 52 | 53 | notFoundJsonException = 54 | new HttpResponseException.Builder(STATUS_CODE_NOT_FOUND, STATUS_MESSAGE, headers).build(); 55 | conflictJsonException = 56 | new HttpResponseException.Builder(409 /* STATUS_CODE_CONFLICT */, STATUS_MESSAGE, headers).build(); 57 | forbiddenJsonException = 58 | new HttpResponseException.Builder(STATUS_CODE_FORBIDDEN, STATUS_MESSAGE, headers).build(); 59 | errorJsonException = 60 | new HttpResponseException.Builder(STATUS_CODE_SERVER_ERROR, STATUS_MESSAGE, headers).build(); 61 | 62 | timeoutException = new SocketTimeoutException(STATUS_MESSAGE); 63 | 64 | underTest = new Executor.Default() { 65 | @Override 66 | public void sleep() { 67 | // Don't really sleep... 68 | } 69 | }; 70 | } 71 | 72 | @Test 73 | public void testVanillaNewExecutor() throws Exception { 74 | assertNotNull(underTest); 75 | when(mockRequest.execute()).thenReturn((Void) null); 76 | 77 | underTest.execute(mockRequest); 78 | } 79 | 80 | @Test(expected = NotFoundException.class) 81 | public void testNewExecutorWithNotFound() throws Exception { 82 | assertNotNull(underTest); 83 | when(mockRequest.execute()).thenThrow(notFoundJsonException); 84 | 85 | underTest.execute(mockRequest); 86 | } 87 | 88 | @Test(expected = ConflictException.class) 89 | public void testNewExecutorWithConflict() throws Exception { 90 | assertNotNull(underTest); 91 | when(mockRequest.execute()).thenThrow(conflictJsonException); 92 | 93 | underTest.execute(mockRequest); 94 | } 95 | 96 | @Test(expected = ForbiddenException.class) 97 | public void testNewExecutorWithForbidden() throws Exception { 98 | assertNotNull(underTest); 99 | when(mockRequest.execute()).thenThrow(forbiddenJsonException); 100 | 101 | underTest.execute(mockRequest); 102 | } 103 | 104 | @Test(expected = HttpResponseException.class) 105 | public void testNewExecutorWithAllErrors() throws Exception { 106 | assertNotNull(underTest); 107 | when(mockRequest.execute()).thenThrow(errorJsonException); 108 | 109 | underTest.execute(mockRequest); 110 | } 111 | 112 | @Test 113 | public void testNewExecutorWithErrorsThenSuccess() throws Exception { 114 | assertNotNull(underTest); 115 | when(mockRequest.execute()) 116 | .thenThrow(errorJsonException) 117 | .thenThrow(timeoutException) 118 | .thenReturn((Void) null); 119 | 120 | underTest.execute(mockRequest); 121 | } 122 | 123 | private static final String STATUS_MESSAGE = "doesn't matter"; 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/util/MetadataReaderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_NOT_FOUND; 19 | import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_OK; 20 | import static com.google.api.client.http.HttpStatusCodes.STATUS_CODE_UNAUTHORIZED; 21 | import static com.google.common.collect.Iterables.getOnlyElement; 22 | import static org.junit.Assert.assertEquals; 23 | import static org.junit.Assert.assertFalse; 24 | import static org.junit.Assert.assertTrue; 25 | import static org.mockito.Mockito.doReturn; 26 | import static org.mockito.Mockito.spy; 27 | import static org.mockito.Mockito.verify; 28 | 29 | import com.google.api.client.testing.http.MockHttpTransport; 30 | import com.google.api.client.testing.http.MockLowLevelHttpRequest; 31 | import com.google.api.client.testing.http.MockLowLevelHttpResponse; 32 | import java.io.IOException; 33 | import org.junit.Before; 34 | import org.junit.Test; 35 | import org.mockito.MockitoAnnotations; 36 | 37 | /** Tests for {@link MetadataReader}. */ 38 | public class MetadataReaderTest { 39 | private MockHttpTransport transport; 40 | private MockLowLevelHttpRequest request; 41 | 42 | private void stubRequest(String url, int statusCode, String responseContent) throws IOException { 43 | request.setResponse( 44 | new MockLowLevelHttpResponse().setStatusCode(statusCode).setContent(responseContent)); 45 | doReturn(request).when(transport).buildRequest("GET", url); 46 | } 47 | 48 | private void verifyRequest(String key) throws IOException { 49 | verify(transport).buildRequest("GET", METADATA_ENDPOINT + key); 50 | verify(request).execute(); 51 | assertEquals("Google", getOnlyElement(request.getHeaderValues("Metadata-Flavor"))); 52 | } 53 | 54 | private MetadataReader underTest; 55 | 56 | @Before 57 | public void setUp() throws Exception { 58 | MockitoAnnotations.initMocks(this); 59 | transport = spy(new MockHttpTransport()); 60 | request = spy(new MockLowLevelHttpRequest()); 61 | 62 | underTest = new MetadataReader.Default(transport.createRequestFactory()); 63 | } 64 | 65 | @Test 66 | public void testHasMetadata() throws Exception { 67 | stubRequest(METADATA_ENDPOINT, STATUS_CODE_OK, "hi"); 68 | 69 | assertTrue(underTest.hasMetadata()); 70 | verifyRequest(""); 71 | } 72 | 73 | @Test 74 | public void testHasNoMetadata() throws Exception { 75 | stubRequest(METADATA_ENDPOINT, STATUS_CODE_NOT_FOUND, "hi"); 76 | 77 | assertFalse(underTest.hasMetadata()); 78 | verifyRequest(""); 79 | } 80 | 81 | @Test 82 | public void testHasNoMetadata2() throws Exception { 83 | stubRequest(METADATA_ENDPOINT, 409, "hi"); 84 | 85 | assertFalse(underTest.hasMetadata()); 86 | verifyRequest(""); 87 | } 88 | 89 | @Test 90 | public void testReadMetadata() throws Exception { 91 | stubRequest(METADATA_ENDPOINT + MY_KEY, STATUS_CODE_OK, MY_VALUE); 92 | 93 | assertEquals(MY_VALUE, underTest.readMetadata(MY_KEY)); 94 | verifyRequest(MY_KEY); 95 | } 96 | 97 | @Test(expected = NotFoundException.class) 98 | public void testReadMissingMetadata() throws Exception { 99 | stubRequest(METADATA_ENDPOINT + MY_KEY, STATUS_CODE_NOT_FOUND, MY_VALUE); 100 | 101 | try { 102 | underTest.readMetadata(MY_KEY); 103 | } finally { 104 | verifyRequest(MY_KEY); 105 | } 106 | } 107 | 108 | @Test(expected = ForbiddenException.class) 109 | public void testReadUnauthorizedMetadata() throws Exception { 110 | stubRequest(METADATA_ENDPOINT + MY_KEY, STATUS_CODE_UNAUTHORIZED, MY_VALUE); 111 | 112 | try { 113 | underTest.readMetadata(MY_KEY); 114 | } finally { 115 | verifyRequest(MY_KEY); 116 | } 117 | } 118 | 119 | @Test(expected = IOException.class) 120 | public void testReadUnrecognizedMetadataException() throws Exception { 121 | stubRequest(METADATA_ENDPOINT + MY_KEY, 409, MY_VALUE); 122 | 123 | try { 124 | underTest.readMetadata(MY_KEY); 125 | } finally { 126 | verifyRequest(MY_KEY); 127 | } 128 | } 129 | 130 | private static String METADATA_ENDPOINT = "http://metadata/computeMetadata/v1"; 131 | private static String MY_KEY = "/my/metadata/path"; 132 | private static String MY_VALUE = "RaNdOm value"; 133 | } 134 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/util/MockExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | import static org.hamcrest.Matchers.containsString; 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertFalse; 21 | import static org.junit.Assert.assertTrue; 22 | import static org.mockito.Mockito.when; 23 | 24 | import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClient; 25 | import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest; 26 | import com.google.common.base.Predicates; 27 | import java.io.IOException; 28 | import org.junit.Before; 29 | import org.junit.Rule; 30 | import org.junit.Test; 31 | import org.junit.rules.ExpectedException; 32 | import org.junit.rules.Verifier; 33 | import org.mockito.Mock; 34 | import org.mockito.MockitoAnnotations; 35 | 36 | /** Tests for the {@link MockExecutor}. */ 37 | public class MockExecutorTest { 38 | 39 | @Rule 40 | public ExpectedException thrown = ExpectedException.none(); 41 | 42 | @Mock 43 | private AbstractGoogleJsonClientRequest mockRequest; 44 | 45 | private static class FakeRequest extends AbstractGoogleJsonClientRequest { 46 | private FakeRequest(AbstractGoogleJsonClient client, String s, String t, Object o) { 47 | super(client, s, t, o, String.class); 48 | } 49 | } 50 | ; 51 | 52 | @Mock 53 | private FakeRequest otherMockRequest; 54 | 55 | private static final String theString = "tHe StRiNg!"; 56 | private static final String theOtherString = "tHe OtHeR sTrInG!"; 57 | 58 | private MockExecutor executor = new MockExecutor(); 59 | 60 | @Rule 61 | public Verifier verifySawAll = new Verifier() { 62 | @Override 63 | public void verify() { 64 | assertTrue(executor.sawAll()); 65 | } 66 | }; 67 | 68 | @Before 69 | public void setUp() throws Exception { 70 | MockitoAnnotations.initMocks(this); 71 | } 72 | 73 | @Test 74 | public void testEmpty() throws Exception { 75 | thrown.expect(IllegalStateException.class); 76 | thrown.expectMessage(containsString("Unexpected request")); 77 | 78 | try { 79 | executor.execute(mockRequest); 80 | } finally { 81 | assertTrue(executor.sawUnexpected()); 82 | } 83 | } 84 | 85 | @Test 86 | public void testFailingPredicate() throws Exception { 87 | // Make sure that when a false predicate occurs, we throw 88 | // an exception 89 | executor.when( 90 | mockRequest.getClass(), 91 | theOtherString, 92 | Predicates.>alwaysFalse()); 93 | 94 | thrown.expect(IllegalStateException.class); 95 | thrown.expectMessage(containsString("User predicate")); 96 | 97 | try { 98 | executor.execute(mockRequest); 99 | } finally { 100 | assertTrue(executor.sawUnexpected()); 101 | } 102 | } 103 | 104 | @Test 105 | public void testWhen() throws Exception { 106 | executor.when(mockRequest.getClass(), theOtherString); 107 | 108 | assertEquals(theOtherString, executor.execute(mockRequest)); 109 | assertFalse(executor.sawUnexpected()); 110 | } 111 | 112 | @Test 113 | public void testOutOfOrder() throws Exception { 114 | executor.when(otherMockRequest.getClass(), theOtherString); 115 | 116 | thrown.expect(IllegalStateException.class); 117 | thrown.expectMessage(containsString("out of order")); 118 | 119 | executor.execute(mockRequest); 120 | assertFalse(executor.sawUnexpected()); 121 | } 122 | 123 | private static final class MyException extends IOException { 124 | public MyException(String message) { 125 | super(message); 126 | } 127 | } 128 | 129 | @Test 130 | public void testThrowWhen() throws Exception { 131 | executor.throwWhen(mockRequest.getClass(), new MyException(theString)); 132 | 133 | thrown.expect(MyException.class); 134 | thrown.expectMessage(theString); 135 | 136 | try { 137 | executor.execute(mockRequest); 138 | } finally { 139 | assertFalse(executor.sawUnexpected()); 140 | } 141 | } 142 | 143 | @Test 144 | public void testPassThruWhen() throws Exception { 145 | when(mockRequest.getJsonContent()).thenReturn(theString); 146 | executor.passThruWhen(mockRequest.getClass()); 147 | 148 | try { 149 | assertEquals(theString, executor.execute(mockRequest)); 150 | } finally { 151 | assertFalse(executor.sawUnexpected()); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/util/NameValuePairTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | import static org.junit.Assert.assertSame; 19 | 20 | import org.junit.Test; 21 | 22 | /** Tests for {@link NameValuePair} */ 23 | public class NameValuePairTest { 24 | @Test 25 | public void testBasicString() { 26 | final String first = "a"; 27 | final String second = "b"; 28 | NameValuePair pair = new NameValuePair(first, second); 29 | 30 | assertSame(first, pair.getName()); 31 | assertSame(second, pair.getValue()); 32 | } 33 | 34 | @Test 35 | public void testBasicWithObject() { 36 | final String first = "a"; 37 | final Object second = new Object(); 38 | NameValuePair pair = new NameValuePair(first, second); 39 | 40 | assertSame(first, pair.getName()); 41 | assertSame(second, pair.getValue()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/google/jenkins/plugins/util/ResolveTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.jenkins.plugins.util; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertThat; 20 | 21 | import java.util.Collections; 22 | import org.hamcrest.Matchers; 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.mockito.MockitoAnnotations; 26 | 27 | /** Tests for {@link Resolve}'s static methods. */ 28 | public class ResolveTest { 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | MockitoAnnotations.initMocks(this); 33 | } 34 | 35 | @Test 36 | public void testBasicResolve() { 37 | String basicInput = "la dee da $BUILD_NUMBER"; 38 | 39 | assertThat(Resolve.resolveBuiltin(basicInput), Matchers.not(Matchers.containsString("BUILD_NUMBER"))); 40 | } 41 | 42 | @Test 43 | public void testUserOverride() { 44 | String basicInput = "$BUILD_NUMBER"; 45 | 46 | assertEquals( 47 | OVERRIDE, 48 | Resolve.resolveBuiltinWithCustom(basicInput, Collections.singletonMap("BUILD_NUMBER", OVERRIDE))); 49 | } 50 | 51 | @Test 52 | public void testJustUserOverrides() { 53 | String basicInput = "$bar"; 54 | 55 | assertEquals(OVERRIDE, Resolve.resolveCustom(basicInput, Collections.singletonMap("bar", OVERRIDE))); 56 | } 57 | 58 | @Test 59 | public void testNoVariable() { 60 | assertEquals(UNKNOWN_VAR, Resolve.resolveBuiltin(UNKNOWN_VAR)); 61 | } 62 | 63 | private static final String OVERRIDE = "my variable override"; 64 | private static final String UNKNOWN_VAR = "$foo"; 65 | } 66 | -------------------------------------------------------------------------------- /src/test/resources/com/google/jenkins/plugins/credentials/oauth/GoogleRobotCredentialsTest/testMigration/credentials.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | my-google-project 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/test/resources/com/google/jenkins/plugins/credentials/oauth/json-service-account-config.yml: -------------------------------------------------------------------------------- 1 | jenkins: 2 | systemMessage: Testing 3 | 4 | credentials: 5 | system: 6 | domainCredentials: 7 | - credentials: 8 | - googleRobotPrivateKey: 9 | projectId: 'test-project' 10 | serviceAccountConfig: 11 | json: 12 | # The contents of test-key.json in base 64. 13 | secretJsonKey: 'eyJ0eXBlIjoic2VydmljZV9hY2NvdW50IiwicHJvamVjdF9pZCI6InRlc3QtcHJvamVjdCIsInBy aXZhdGVfa2V5X2lkIjoidGVzdC1wcml2YXRlLWtleS1pZCIsInByaXZhdGVfa2V5IjoidGVzdC1w cml2YXRlLWtleSIsImNsaWVudF9lbWFpbCI6InRlc3QtYWNjb3VudEB0ZXN0LXByb2plY3QuaWFt LmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJ0ZXN0LWNsaWVudC1pZCIsImF1dGhf dXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91 cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJf eDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0 cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9i b3QvdjEvbWV0YWRhdGEveDUwOS90ZXN0LWFjY291bnQlNDB0ZXN0LXByb2plY3QuaWFtLmdzZXJ2 aWNlYWNjb3VudC5jb20ifQ==' -------------------------------------------------------------------------------- /src/test/resources/com/google/jenkins/plugins/credentials/oauth/p12-service-account-config.yml: -------------------------------------------------------------------------------- 1 | jenkins: 2 | systemMessage: Testing 3 | 4 | credentials: 5 | system: 6 | domainCredentials: 7 | - credentials: 8 | - googleRobotPrivateKey: 9 | projectId: 'test-project' 10 | serviceAccountConfig: 11 | p12: 12 | emailAddress: 'test-account@test-project.iam.gserviceaccount.com' 13 | # 'test-p12-key' in base 64. 14 | secretP12Key: 'dGVzdC1wMTIta2V5' 15 | -------------------------------------------------------------------------------- /src/test/resources/com/google/jenkins/plugins/credentials/oauth/sample-privatekey.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/google-oauth-plugin/31633a5e08313b7871be6e4622b5632699628e98/src/test/resources/com/google/jenkins/plugins/credentials/oauth/sample-privatekey.p12 -------------------------------------------------------------------------------- /src/test/resources/com/google/jenkins/plugins/credentials/oauth/test-key.json: -------------------------------------------------------------------------------- 1 | {"type":"service_account","project_id":"test-project","private_key_id":"test-private-key-id","private_key":"test-private-key","client_email":"test-account@test-project.iam.gserviceaccount.com","client_id":"test-client-id","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/test-account%40test-project.iam.gserviceaccount.com"} --------------------------------------------------------------------------------