├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── ISSUE_TEMPLATE ├── LICENSE ├── README.md ├── README_azurepiplines.md ├── README_hipchat.md ├── README_jenkins.md ├── README_teamcity.md ├── _config.yml ├── build.sh ├── export_and_run.sh ├── package-lock.json ├── package.json ├── pom.xml ├── release.sh ├── run-bitbucketserver-in-docker.sh ├── sandbox ├── all.png ├── closer.png ├── hipchat.png ├── rendered_form.png └── repo.png ├── setup-atlassian-sdk.sh └── src ├── main ├── java │ └── se │ │ └── bjurr │ │ └── prnfb │ │ ├── http │ │ ├── ClientKeyStore.java │ │ ├── HttpResponse.java │ │ ├── Invoker.java │ │ ├── NotificationResponse.java │ │ └── UrlInvoker.java │ │ ├── listener │ │ ├── PrnfbPullRequestAction.java │ │ └── PrnfbPullRequestEventListener.java │ │ ├── presentation │ │ ├── ButtonServlet.java │ │ ├── GlobalAdminServlet.java │ │ ├── NotificationServlet.java │ │ ├── SettingsDataServlet.java │ │ └── dto │ │ │ ├── ButtonDTO.java │ │ │ ├── ButtonFormElementDTO.java │ │ │ ├── ButtonFormElementOptionDTO.java │ │ │ ├── ButtonFormType.java │ │ │ ├── ButtonPressDTO.java │ │ │ ├── HeaderDTO.java │ │ │ ├── NotificationDTO.java │ │ │ ├── NotificationResponseDTO.java │ │ │ ├── ON_OR_OFF.java │ │ │ └── SettingsDataDTO.java │ │ ├── service │ │ ├── ButtonsService.java │ │ ├── JsonEscaper.java │ │ ├── PrnfbRenderer.java │ │ ├── PrnfbRendererFactory.java │ │ ├── PrnfbRendererWrapper.java │ │ ├── PrnfbVariable.java │ │ ├── PrnfbVariableResolver.java │ │ ├── RepoProtocol.java │ │ ├── SettingsService.java │ │ ├── UserCheckService.java │ │ └── VariablesContext.java │ │ ├── settings │ │ ├── HasUuid.java │ │ ├── PrnfbButton.java │ │ ├── PrnfbButtonFormElement.java │ │ ├── PrnfbButtonFormElementOption.java │ │ ├── PrnfbHeader.java │ │ ├── PrnfbNotification.java │ │ ├── PrnfbNotificationBuilder.java │ │ ├── PrnfbSettings.java │ │ ├── PrnfbSettingsBuilder.java │ │ ├── PrnfbSettingsData.java │ │ ├── PrnfbSettingsDataBuilder.java │ │ ├── Restricted.java │ │ ├── TRIGGER_IF_MERGE.java │ │ ├── USER_LEVEL.java │ │ └── ValidationException.java │ │ └── transformer │ │ ├── ButtonTransformer.java │ │ ├── NotificationTransformer.java │ │ └── SettingsTransformer.java └── resources │ ├── 3rdparty.js │ ├── admin.css │ ├── admin.js │ ├── admin.vm │ ├── atlassian-plugin.xml │ ├── images │ ├── banner.png │ ├── pluginIcon.png │ └── pluginLogo.png │ ├── pr-triggerbutton.js │ ├── prnfs.properties │ └── utils.js └── test └── java └── se └── bjurr └── prnfb ├── http ├── ClientKeyStoreTest.java └── UrlInvokerTest.java ├── listener ├── FakeExecutorService.java └── PrnfbPullRequestEventListenerTest.java ├── presentation ├── ButtonServletTest.java ├── GlobalAdminServletTest.java ├── NotificationServletTest.java └── SettingsDataServletTest.java ├── service ├── ButtonsServiceTest.java ├── JsonEscaperTest.java ├── MockedEscalatedSecurityContext.java ├── PluginSettingsMap.java ├── PrnfbRendererTest.java ├── PrnfbVariableTest.java ├── SettingsServiceTest.java ├── UserCheckServiceTest.java └── VariablesContextTest.java ├── settings └── PrnfbNotificationBuilderTest.java ├── test └── Podam.java └── transformer ├── ButtonTransformerTest.java ├── NotificationTransformerTest.java └── SettingsDataTransformerTest.java /.gitattributes: -------------------------------------------------------------------------------- 1 | * text 2 | 3 | *.png binary 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [tomasbjerre] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .project 3 | .classpath 4 | .settings 5 | *~ 6 | .okhttpcache 7 | node_modules 8 | opt 9 | atlassian-plugin-sdk-tgz 10 | .idea 11 | *iml 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: java 3 | jdk: 4 | - openjdk8 5 | 6 | before_install: 7 | - ./setup-atlassian-sdk.sh `pwd` 8 | - export PATH=opt/atlassian-plugin-sdk/bin:opt/atlassian-plugin-sdk/apache-maven-*/bin:$PATH 9 | install: 10 | - atlas-mvn install -Dbitbucket.version=5.1.0 11 | - atlas-mvn install 12 | script: 13 | - atlas-package -Dbitbucket.version=5.1.0 14 | - atlas-package 15 | notifications: 16 | email: false 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Inspect DB 2 | 3 | * Turn off BBS 4 | * java -cp ./target/bitbucket/app/WEB-INF/lib/h2-1.3.176.jar org.h2.tools.Shell 5 | * User: sa 6 | * Driver Leave blank, just enter 7 | * URL: jdbc:h2:file:///home/bjerre/workspace/pull-request-notifier-for-bitbucket/target/bitbucket/home/shared/data/db;DB_CLOSE_ON_EXIT=TRUE 8 | * Password Leave blank, just enter 9 | * maxwidth 9999 10 | * SELECT * FROM PLUGIN_SETTING WHERE KEY_NAME LIKE '%pull%'; 11 | * SELECT KEY_VALUE FROM PLUGIN_SETTING WHERE KEY_NAME='se.bjurr.prnfb.pull-request-notifier-for-bitbucket-3' 12 | 13 | # Developer instructions 14 | 15 | The .travis.yml is setting up Atlas SDK and building the plugin. It may help you setup your environment. 16 | 17 | Prerequisites: 18 | 19 | * Atlas SDK [(installation instructions)](https://developer.atlassian.com/docs/getting-started/set-up-the-atlassian-plugin-sdk-and-build-a-project). 20 | * JDK 1.8 or newer 21 | 22 | Generate Eclipse project: 23 | ``` 24 | atlas-mvn eclipse:eclipse 25 | ``` 26 | 27 | Package the plugin: 28 | ``` 29 | atlas-package 30 | ``` 31 | 32 | Run Bitbucket, with the plugin, on localhost: 33 | ``` 34 | export MAVEN_OPTS=-Dplugin.resource.directories=`pwd`/src/main/resources 35 | atlas-run 36 | ``` 37 | 38 | You can also remote debug on port 5005 with: 39 | ``` 40 | atlas-debug 41 | ``` 42 | 43 | Make a release [(detailed instructions)](https://developer.atlassian.com/docs/common-coding-tasks/development-cycle/packaging-and-releasing-your-plugin): 44 | ``` 45 | mvn -B release:prepare -DperformRelease=true release:perform 46 | ``` 47 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | 5 | jshint: { 6 | all: [ 7 | 'src/main/resources/pr-triggerbutton.js', 8 | 'src/main/resources/admin.js', 9 | 'src/main/resources/utils.js' 10 | ], 11 | options: { 12 | esversion: 3 13 | } 14 | }, 15 | 16 | jsbeautifier: { 17 | files: ["Gruntfile.js", 18 | "src/**/*.vm", 19 | "src/**/*.xml", 20 | "pom.xml", 21 | "src/**/*.js", 22 | "Gruntfile.js", 23 | "src/**/*.css" 24 | ], 25 | options: { 26 | html: { 27 | fileTypes: [".vm", ".xml"], 28 | indentChar: " ", 29 | indentSize: 1, 30 | preserveNewlines: true, 31 | unformatted: ["a", "sub", "sup", "b", "i", "u", "strong"] 32 | }, 33 | js: { 34 | indentChar: " ", 35 | indentSize: 1 36 | } 37 | } 38 | } 39 | }); 40 | 41 | grunt.loadNpmTasks("grunt-jsbeautifier"); 42 | grunt.loadNpmTasks('grunt-contrib-jshint'); 43 | 44 | grunt.registerTask('default', ['jsbeautifier', 'jshint']); 45 | }; 46 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | If things don't work as you expect, perhaps you should file an issue. But first, try troubleshooting it and provide as much info as possible. Here are some things that may help if added to an issue. 2 | 3 | * Plugin version used. 4 | * Bitbucket Server version used. 5 | * Stack traces in Bitbucket Server log file. 6 | * Any browser console log messages, you can find it in Developer Tools in Chome by pressing F12. 7 | * Your configuration: 8 | * http://localhost:7990/bitbucket/rest/prnfb-admin/1.0/settings 9 | * http://localhost:7990/bitbucket/rest/prnfb-admin/1.0/settings/buttons 10 | * http://localhost:7990/bitbucket/rest/prnfb-admin/1.0/settings/notifications 11 | * If the system you are trying to notify does not seem to get notified: 12 | * Check that the triggered URL looks as expected. You can do that by invoking https://requestb.in/ and inspect its results. 13 | * Try to SSH to the Bitbucket Server machine and run same URL with Curl to make sure it's not a firewall issue. 14 | * Enable runtime debug logging of 'se.bjurr.prnfb.http.UrlInvoker' following https://confluence.atlassian.com/bitbucketserver/bitbucket-server-debug-logging-776640147.html 15 | 16 | If you do not have access to the server log files, one idea is to: 17 | 18 | * Also trigger on button pressed, and add a button 19 | * Open a PR 20 | * Open Chrome developer tools, F12 21 | * Press the button in the PR 22 | * Check the response in the network tab in Chrome. See if it contains something interesting 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 Tomas Bjerre 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 | -------------------------------------------------------------------------------- /README_azurepiplines.md: -------------------------------------------------------------------------------- 1 | # Azure Pipelines 2 | 3 | Here is how to integrate with Azure Pipelines (VSTS). 4 | 5 | * URL should be set to `https://.visualstudio.com//_apis/build/builds?api-version=5.0` or `https://dev.azure.com///_apis/build/builds?api-version=5.0` 6 | * Method should be **POST** 7 | * Post content should be: 8 | ``` 9 | { 10 | "definition": { 11 | "id": 12 | } 13 | "reason": "pullRequest", 14 | "sourceBranch": "${PULL_REQUEST_FROM_ID}", 15 | "sourceVersion": "${PULL_REQUEST_FROM_HASH}", 16 | "triggerInfo": { 17 | "url": "${PULL_REQUEST_URL}", 18 | "user": "${PULL_REQUEST_USER_EMAIL_ADDRESS}", 19 | "title": "${PULL_REQUEST_TITLE}", 20 | "version": "${PULL_REQUEST_VERSION}", 21 | "author": "${PULL_REQUEST_AUTHOR_EMAIL}", 22 | "reviewers": "${PULL_REQUEST_REVIEWERS_EMAIL}" 23 | } 24 | } 25 | ``` 26 | * Encode post content as JSON 27 | * In **Headers** add "Content-Type" with value "application/json" 28 | 29 | Additional data can be sent in the post. See [Azure API Docs](https://docs.microsoft.com/en-us/rest/api/azure/devops/build/Builds/Queue?view=azure-devops-rest-5.0) for more info. The triggerInfo collection is not shown in the VSTS UI, but is returned in API calls. 30 | 31 | It's a good idea to use a personal access token from a service principal for authentication. The queued builds will all state that they were triggered by the owner of the token. 32 | -------------------------------------------------------------------------------- /README_hipchat.md: -------------------------------------------------------------------------------- 1 | # Send Pull Request Notifications to HipChat Room 2 | 3 | 1. Provision an API Token for HipChat 4 | - Login to the Hipchat website 5 | - Click the `Edit Profile` button in the top-right 6 | - Click `API Access` in the navigation menu on the left 7 | - Enter some descriptive text for the `Label`, select `Send Notification` from the `Scopes` combo box, and click the `Create` button 8 | - Copy the token from the grid; this is needed to authenticate the request 9 | > **Note:** This value allows API requests to be authenticated as your HipChat user for the selected actions. For that reason, this token should be kept private! It is recommended to create individual tokens for every application that uses the HipChat API, to minimize risk if the token is compromised. 10 | 11 | 1. Get the `API ID` for the HipChat room that will receive notifications 12 | - Login to the Hipchat website 13 | - Click on `Rooms` in the navigation bar below the welcome banner 14 | - Under the `Active` tab, click the name of the room that will receive notifications 15 | - Copy the `API ID` from the grid, this will be included in the notification request 16 | 17 | 1. Configure Bitbucket to send notifications to a HipChat room 18 | - In Bitbucket, go to the repository that will trigger HipChat notifications 19 | - Click `Settings` (a gear icon, if the sidebar is not expanded) 20 | - Click `Pull request notifications` under the `ADD-ONS` heading in the navigation menu on the left. 21 | - Scroll down to `Notifications`. 22 | - Configure triggers as desired 23 | - Configure the URL and Headers sections to communicate with HipChat: 24 | - **URL**: `https://api.hipchat.com/v2/room/{roomId}/notification` 25 | > Use the `API ID` of the room in place of `{roomId}` in the url above. 26 | 27 | - **Post content**: 28 | ```json 29 | { 30 | "from": "Bitbucket Pull Request", 31 | "color": "green", 32 | "message_format": "text", 33 | "message": "${PULL_REQUEST_DESCRIPTION}", 34 | "card": { 35 | "id": "${PULL_REQUEST_FROM_HASH}", 36 | "style": "link", 37 | "description": { 38 | "value": "${PULL_REQUEST_DESCRIPTION}", 39 | "format": "text" 40 | }, 41 | "format": "compact", 42 | "notify": false, 43 | "url": "${PULL_REQUEST_URL}", 44 | "title": "[${PULL_REQUEST_FROM_REPO_NAME}] ${PULL_REQUEST_TITLE} (#${PULL_REQUEST_ID})" 45 | } 46 | } 47 | ``` 48 | - **Post content encoding** 49 | Check the *HTML encode* checkbox. 50 | - **Headers**: 51 | 52 | | Name | Value | 53 | | :-------------- | :------------------ | 54 | | `Content-Type` | `application/json` | 55 | | `Authorization` | `Bearer {apiToken}` | 56 | > Use the `API Token` in place of `{apiToken}` in the above value. 57 | 58 | 1. Trigger your notification by performing one of the actions you configured earlier 59 | ![](https://raw.githubusercontent.com/tomasbjerre/pull-request-notifier-for-bitbucket/master/sandbox/hipchat.png) 60 | 61 | For further customization of the HipChat notification, refer to the [official documentation](https://www.hipchat.com/docs/apiv2/method/send_room_notification). 62 | -------------------------------------------------------------------------------- /README_jenkins.md: -------------------------------------------------------------------------------- 1 | # Jenkins 2 | Parameterized Jenkins jobs can be triggered remotely by invoking a URL. How you trigger your Jenkins installation may vary depending on how it is configured. Here is, probably, the most complicated scenario where there is CSRF protection and authentication requirements. 3 | 4 | The job that you want to trigger must have: 5 | * *This build is parameterized* checkbox checked. 6 | * *Trigger builds remotely* checkbox checked. 7 | * You may, or may not, use a token here. 8 | 9 | There is a full job-dsl for this in [here](https://github.com/jenkinsci/violation-comments-to-stash-plugin). 10 | 11 | I like to add an *Execute shell* build step and then just do `echo param: $paramName` to test that my parameter shows up in the build job log. 12 | 13 | First, you may try to trigger Jenkins with [Curl](https://github.com/curl/curl) from command line and then, when you know how it should be done, configure the plugin. 14 | 15 | If your Jenkins is CSRF protected, you need to get a crumb. It can be done like this. 16 | ``` 17 | curl -s 'http://JENKINS_HOSTNAME/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)' 18 | ``` 19 | 20 | The response should be something like `Jenkins-Crumb:f122c77298b349b0116140265418ec7f`. 21 | 22 | Now you can trigger a build like this (just remove `?token=YOUR_TOKEN` if you are not using a token). 23 | 24 | ``` 25 | curl -u USERNAME:PASSWORD -X POST --data "paramName=paramValue" -H "Jenkins-Crumb:f122c77298b349b0116140265418ec7f" http://JENKINS_HOSTNAME/job/JENKINS_JOB/buildWithParameters?token=YOUR_TOKEN 26 | ``` 27 | 28 | Now that job should have been triggered and you should be able to verify that Jenkins is setup correclty. You may want to SSH to the Bitbucket Server machine and do this, to also verify that firewalls are open. 29 | 30 | Now to configure the plugin! 31 | 32 | If you need ***authentication***, add your username and password in *Basic authentication*. 33 | 34 | If you are using a ***CSRF*** protection in Jenkins, you can use the **Injection URL** feature. 35 | * Set **Injection URL** field to `http://JENKINS_HOSTNAME/crumbIssuer/api/xml?xpath=//crumb/text()`. 36 | * You may get an error like *primitive XPath result sets forbidden; implement jenkins.security.SecureRequester*. If so, you can set Injection URL to `http://JENKINS/crumbIssuer/api/xml?xpath=//crumb` in combination with regular expression `([^<]*)`. 37 | * A third option is to checkout [this](https://wiki.jenkins-ci.org/display/JENKINS/Secure+Requester+Whitelist+Plugin) Jenkins plugin. 38 | * In the headers section, set header **Jenkins-Crumb** with value **${INJECTION_URL_VALUE}**. The `Jenkins-Crumb` header name was previously just `.crumb`, use whatever the `curl` command responded with above. 39 | 40 | You may trigger the build with `GET` or `POST`. 41 | 42 | In ***URL*** add `http://JENKINS_HOSTNAME/job/JENKINS_JOB/buildWithParameters?token=YOUR_TOKEN¶mName=paramValue`. 43 | 44 | Thats it! There are some common mistakes. 45 | * If using ${EVERYTHING_URL}, like `...?token=token&${EVERYTHING_URL}` then in your jenkins job you have to have parameters for each parameter, like `PULL_REQUEST_URL`. 46 | * Even when using `POST`, you should add the parameters to the `URL`. 47 | 48 | #### Jenkins build step 49 | To perform the merge and verify that the pull request builds in its target branch, I do something like this. 50 | 51 | ``` 52 | git clone $TO_REPO 53 | cd * 54 | git reset --hard $TO_HASH 55 | git status 56 | git remote add from $FROM_REPO 57 | git fetch --all 58 | git merge $FROM_HASH 59 | git --no-pager log --max-count=10 --graph --abbrev-commit 60 | 61 | #compile command here ... 62 | ``` 63 | -------------------------------------------------------------------------------- /README_teamcity.md: -------------------------------------------------------------------------------- 1 | # TeamCity 2 | 3 | Here is how to integrate with TeamCity. 4 | 5 | * URL should be set to `https://youserver/httpAuth/app/rest/buildQueue` 6 | * Method should be **POST** 7 | * Post content should be: 8 | ``` 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | * Don't encode post content 23 | * In **Headers** add "Content-Type" with value "application/xml" 24 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npm install 3 | node_modules/.bin/grunt 4 | #atlas-mvn versions:update-properties 5 | atlas-mvn package verify 6 | -------------------------------------------------------------------------------- /export_and_run.sh: -------------------------------------------------------------------------------- 1 | export MAVEN_OPTS=-Dplugin.resource.directories=`pwd`/src/main/resources 2 | #atlas-mvn versions:update-properties 3 | atlas-run || mvn bitbucket:run 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "grunt": "0.4.5", 4 | "grunt-cli": "0.1.13", 5 | "grunt-contrib-jshint": "^1.0.0", 6 | "grunt-jsbeautifier": "0.2.10" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #atlas-mvn versions:update-properties 3 | atlas-mvn release:prepare release:perform -B || exit 1 4 | ./build.sh 5 | git commit -a --amend --no-edit 6 | git push -f 7 | -------------------------------------------------------------------------------- /run-bitbucketserver-in-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run -p 7990:7990 atlassian/bitbucket-server 3 | -------------------------------------------------------------------------------- /sandbox/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasbjerre/pull-request-notifier-for-bitbucket/c3043df6f5477a4fc0db0167f072d91ffb462a6a/sandbox/all.png -------------------------------------------------------------------------------- /sandbox/closer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasbjerre/pull-request-notifier-for-bitbucket/c3043df6f5477a4fc0db0167f072d91ffb462a6a/sandbox/closer.png -------------------------------------------------------------------------------- /sandbox/hipchat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasbjerre/pull-request-notifier-for-bitbucket/c3043df6f5477a4fc0db0167f072d91ffb462a6a/sandbox/hipchat.png -------------------------------------------------------------------------------- /sandbox/rendered_form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasbjerre/pull-request-notifier-for-bitbucket/c3043df6f5477a4fc0db0167f072d91ffb462a6a/sandbox/rendered_form.png -------------------------------------------------------------------------------- /sandbox/repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasbjerre/pull-request-notifier-for-bitbucket/c3043df6f5477a4fc0db0167f072d91ffb462a6a/sandbox/repo.png -------------------------------------------------------------------------------- /setup-atlassian-sdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | installdir=$1 4 | if [ $# -eq 0 ]; then 5 | installdir=~ 6 | fi 7 | 8 | echo Installing in $installdir 9 | 10 | cd $installdir 11 | wget https://marketplace.atlassian.com/download/plugins/atlassian-plugin-sdk-tgz 12 | mkdir opt 13 | tar -xvzf *plugin-sdk* -C opt 14 | mv opt/*plugin-sdk* opt/atlassian-plugin-sdk 15 | chmod a+x opt/atlassian-plugin-sdk/bin/* 16 | chmod a+x opt/atlassian-plugin-sdk/apache-maven-*/bin/* 17 | echo "export PATH=$installdir/opt/atlassian-plugin-sdk/bin:$installdir/opt/atlassian-plugin-sdk/apache-maven-*/bin:$PATH" >> ~/.bashrc 18 | source ~/.bashrc 19 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/http/ClientKeyStore.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.http; 2 | 3 | import static com.google.common.base.Optional.fromNullable; 4 | 5 | import com.google.common.base.Optional; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.security.KeyStore; 9 | import java.security.KeyStoreException; 10 | import se.bjurr.prnfb.settings.PrnfbSettingsData; 11 | 12 | /** 13 | * A keystore based on the definition from the application properties.
14 | *
15 | * Inspired by:
16 | * Philip Dodds (pdodds) https://github.com/pdodds 17 | */ 18 | public class ClientKeyStore { 19 | 20 | private KeyStore keyStore = null; 21 | private char[] password = null; 22 | 23 | public ClientKeyStore(PrnfbSettingsData settings) { 24 | if (settings.getKeyStore().isPresent()) { 25 | File keyStoreFile = new File(settings.getKeyStore().get()); 26 | try { 27 | this.keyStore = getKeyStore(settings.getKeyStoreType()); 28 | 29 | if (settings.getKeyStorePassword().isPresent()) { 30 | this.password = settings.getKeyStorePassword().get().toCharArray(); 31 | } 32 | 33 | this.keyStore.load(new FileInputStream(keyStoreFile), this.password); 34 | } catch (Exception e) { 35 | throw new RuntimeException( 36 | "Unable to build keystore from " + keyStoreFile.getAbsolutePath(), e); 37 | } 38 | } 39 | } 40 | 41 | public Optional getKeyStore() { 42 | return fromNullable(this.keyStore); 43 | } 44 | 45 | public char[] getPassword() { 46 | return this.password; 47 | } 48 | 49 | private KeyStore getKeyStore(String keyStoreType) throws KeyStoreException { 50 | if (keyStoreType != null) { 51 | return KeyStore.getInstance(keyStoreType); 52 | } else { 53 | return KeyStore.getInstance(KeyStore.getDefaultType()); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/http/HttpResponse.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.http; 2 | 3 | import java.net.URI; 4 | 5 | public class HttpResponse { 6 | private final String content; 7 | 8 | private final int status; 9 | 10 | private final URI uri; 11 | 12 | public HttpResponse(URI uri, int status, String content) { 13 | this.uri = uri; 14 | this.status = status; 15 | this.content = content; 16 | } 17 | 18 | @Override 19 | public boolean equals(Object obj) { 20 | if (this == obj) { 21 | return true; 22 | } 23 | if (obj == null) { 24 | return false; 25 | } 26 | if (getClass() != obj.getClass()) { 27 | return false; 28 | } 29 | HttpResponse other = (HttpResponse) obj; 30 | if (this.content == null) { 31 | if (other.content != null) { 32 | return false; 33 | } 34 | } else if (!this.content.equals(other.content)) { 35 | return false; 36 | } 37 | if (this.status != other.status) { 38 | return false; 39 | } 40 | if (this.uri == null) { 41 | if (other.uri != null) { 42 | return false; 43 | } 44 | } else if (!this.uri.equals(other.uri)) { 45 | return false; 46 | } 47 | return true; 48 | } 49 | 50 | public String getContent() { 51 | return this.content; 52 | } 53 | 54 | public int getStatus() { 55 | return this.status; 56 | } 57 | 58 | public URI getUri() { 59 | return this.uri; 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | final int prime = 31; 65 | int result = 1; 66 | result = prime * result + ((this.content == null) ? 0 : this.content.hashCode()); 67 | result = prime * result + this.status; 68 | result = prime * result + ((this.uri == null) ? 0 : this.uri.hashCode()); 69 | return result; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return "HttpResponse [content=" 75 | + this.content 76 | + ", status=" 77 | + this.status 78 | + ", uri=" 79 | + this.uri 80 | + "]"; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/http/Invoker.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.http; 2 | 3 | public interface Invoker { 4 | HttpResponse invoke(UrlInvoker urlInvoker); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/http/NotificationResponse.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.http; 2 | 3 | import java.util.UUID; 4 | 5 | public class NotificationResponse { 6 | private final HttpResponse httpResponse; 7 | private final UUID notification; 8 | private final String notificationName; 9 | 10 | public NotificationResponse( 11 | UUID notification, String notificationName, HttpResponse httpResponse) { 12 | this.notification = notification; 13 | this.notificationName = notificationName; 14 | this.httpResponse = httpResponse; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object obj) { 19 | if (this == obj) { 20 | return true; 21 | } 22 | if (obj == null) { 23 | return false; 24 | } 25 | if (getClass() != obj.getClass()) { 26 | return false; 27 | } 28 | NotificationResponse other = (NotificationResponse) obj; 29 | if (this.httpResponse == null) { 30 | if (other.httpResponse != null) { 31 | return false; 32 | } 33 | } else if (!this.httpResponse.equals(other.httpResponse)) { 34 | return false; 35 | } 36 | if (this.notification == null) { 37 | if (other.notification != null) { 38 | return false; 39 | } 40 | } else if (!this.notification.equals(other.notification)) { 41 | return false; 42 | } 43 | if (this.notificationName == null) { 44 | if (other.notificationName != null) { 45 | return false; 46 | } 47 | } else if (!this.notificationName.equals(other.notificationName)) { 48 | return false; 49 | } 50 | return true; 51 | } 52 | 53 | public HttpResponse getHttpResponse() { 54 | return this.httpResponse; 55 | } 56 | 57 | public UUID getNotification() { 58 | return this.notification; 59 | } 60 | 61 | public String getNotificationName() { 62 | return this.notificationName; 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | final int prime = 31; 68 | int result = 1; 69 | result = prime * result + ((this.httpResponse == null) ? 0 : this.httpResponse.hashCode()); 70 | result = prime * result + ((this.notification == null) ? 0 : this.notification.hashCode()); 71 | result = 72 | prime * result + ((this.notificationName == null) ? 0 : this.notificationName.hashCode()); 73 | return result; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return "NotificationResponse [httpResponse=" 79 | + this.httpResponse 80 | + ", notification=" 81 | + this.notification 82 | + ", notificationName=" 83 | + this.notificationName 84 | + "]"; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/listener/PrnfbPullRequestAction.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.listener; 2 | 3 | import com.atlassian.bitbucket.event.pull.PullRequestEvent; 4 | import com.atlassian.bitbucket.event.pull.PullRequestRescopedEvent; 5 | import se.bjurr.prnfb.settings.PrnfbNotification; 6 | 7 | public enum PrnfbPullRequestAction { 8 | APPROVED, // 9 | BUTTON_TRIGGER, // 10 | COMMENTED, // 11 | DELETED, // 12 | DECLINED, // 13 | MERGED, // 14 | OPENED, // 15 | REOPENED, // 16 | RESCOPED, // 17 | RESCOPED_FROM, // 18 | RESCOPED_TO, // 19 | UNAPPROVED, // 20 | UPDATED, 21 | REVIEWED; // 22 | 23 | public static PrnfbPullRequestAction fromPullRequestEvent( 24 | PullRequestEvent event, PrnfbNotification notification) { 25 | if (event instanceof PullRequestRescopedEvent) { 26 | final PullRequestRescopedEvent rescopedEvent = (PullRequestRescopedEvent) event; 27 | final boolean toChanged = 28 | !rescopedEvent 29 | .getPreviousToHash() 30 | .equals(rescopedEvent.getPullRequest().getToRef().getLatestCommit()); 31 | final boolean fromChanged = 32 | !rescopedEvent 33 | .getPreviousFromHash() 34 | .equals(rescopedEvent.getPullRequest().getFromRef().getLatestCommit()); 35 | if (fromChanged && !toChanged) { 36 | return RESCOPED_FROM; 37 | } else if (toChanged && !fromChanged) { 38 | return RESCOPED_TO; 39 | } else { 40 | if (notification.getTriggers().contains(RESCOPED_FROM)) { 41 | return RESCOPED_FROM; 42 | } else if (notification.getTriggers().contains(RESCOPED_TO)) { 43 | return RESCOPED_TO; 44 | } 45 | } 46 | } 47 | return PrnfbPullRequestAction.valueOf(event.getAction().name()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/ButtonServlet.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation; 2 | 3 | import static com.google.common.base.Strings.isNullOrEmpty; 4 | import static javax.ws.rs.core.MediaType.APPLICATION_JSON; 5 | import static javax.ws.rs.core.Response.Status.NOT_FOUND; 6 | import static javax.ws.rs.core.Response.Status.OK; 7 | import static javax.ws.rs.core.Response.Status.UNAUTHORIZED; 8 | import static javax.ws.rs.core.Response.ok; 9 | import static javax.ws.rs.core.Response.status; 10 | import static se.bjurr.prnfb.transformer.ButtonTransformer.toButtonDto; 11 | import static se.bjurr.prnfb.transformer.ButtonTransformer.toButtonDtoList; 12 | import static se.bjurr.prnfb.transformer.ButtonTransformer.toPrnfbButton; 13 | import static se.bjurr.prnfb.transformer.ButtonTransformer.toTriggerResultDto; 14 | 15 | import com.atlassian.annotations.security.XsrfProtectionExcluded; 16 | import com.google.common.base.Optional; 17 | import com.google.common.collect.Iterables; 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.UUID; 21 | import javax.servlet.http.HttpServletRequest; 22 | import javax.ws.rs.Consumes; 23 | import javax.ws.rs.DELETE; 24 | import javax.ws.rs.GET; 25 | import javax.ws.rs.POST; 26 | import javax.ws.rs.Path; 27 | import javax.ws.rs.PathParam; 28 | import javax.ws.rs.Produces; 29 | import javax.ws.rs.core.Context; 30 | import javax.ws.rs.core.Response; 31 | import se.bjurr.prnfb.http.NotificationResponse; 32 | import se.bjurr.prnfb.presentation.dto.ButtonDTO; 33 | import se.bjurr.prnfb.presentation.dto.ButtonFormElementDTO; 34 | import se.bjurr.prnfb.presentation.dto.ButtonPressDTO; 35 | import se.bjurr.prnfb.service.ButtonsService; 36 | import se.bjurr.prnfb.service.PrnfbRenderer.ENCODE_FOR; 37 | import se.bjurr.prnfb.service.PrnfbRendererWrapper; 38 | import se.bjurr.prnfb.service.SettingsService; 39 | import se.bjurr.prnfb.service.UserCheckService; 40 | import se.bjurr.prnfb.settings.PrnfbButton; 41 | import se.bjurr.prnfb.settings.USER_LEVEL; 42 | 43 | @Path("/settings/buttons") 44 | public class ButtonServlet { 45 | 46 | private final ButtonsService buttonsService; 47 | private final SettingsService settingsService; 48 | private final UserCheckService userCheckService; 49 | 50 | public ButtonServlet( 51 | ButtonsService buttonsService, 52 | SettingsService settingsService, 53 | UserCheckService userCheckService) { 54 | this.buttonsService = buttonsService; 55 | this.settingsService = settingsService; 56 | this.userCheckService = userCheckService; 57 | } 58 | 59 | @POST 60 | @XsrfProtectionExcluded 61 | @Consumes(APPLICATION_JSON) 62 | @Produces(APPLICATION_JSON) 63 | public Response create(ButtonDTO buttonDto) { 64 | final USER_LEVEL adminRestriction = 65 | settingsService.getPrnfbSettingsData().getAdminRestriction(); 66 | if (!userCheckService.isAdminAllowed(buttonDto, adminRestriction)) { 67 | return status(UNAUTHORIZED) // 68 | .build(); 69 | } 70 | 71 | final PrnfbButton prnfbButton = toPrnfbButton(buttonDto); 72 | final PrnfbButton created = settingsService.addOrUpdateButton(prnfbButton); 73 | final ButtonDTO createdDto = toButtonDto(created); 74 | 75 | return status(OK) // 76 | .entity(createdDto) // 77 | .build(); 78 | } 79 | 80 | @DELETE 81 | @Path("{uuid}") 82 | @XsrfProtectionExcluded 83 | @Produces(APPLICATION_JSON) 84 | public Response delete(@PathParam("uuid") UUID prnfbButtonUuid) { 85 | final PrnfbButton prnfbButton = settingsService.getButton(prnfbButtonUuid); 86 | final USER_LEVEL adminRestriction = 87 | settingsService.getPrnfbSettingsData().getAdminRestriction(); 88 | if (!userCheckService.isAdminAllowed(prnfbButton, adminRestriction)) { 89 | return status(UNAUTHORIZED) // 90 | .build(); 91 | } 92 | settingsService.deleteButton(prnfbButtonUuid); 93 | return status(OK).build(); 94 | } 95 | 96 | @GET 97 | @Produces(APPLICATION_JSON) 98 | public Response get() { 99 | final List buttons = settingsService.getButtons(); 100 | final Iterable allowedButtons = userCheckService.filterAdminAllowed(buttons); 101 | final List dtos = toButtonDtoList(allowedButtons); 102 | Collections.sort(dtos); 103 | return ok(dtos, APPLICATION_JSON).build(); 104 | } 105 | 106 | @GET 107 | @Path("/projectKey/{projectKey}") 108 | @Produces(APPLICATION_JSON) 109 | public Response get(@PathParam("projectKey") String projectKey) { 110 | final List buttons = settingsService.getButtons(projectKey); 111 | final Iterable allowedButtons = userCheckService.filterAdminAllowed(buttons); 112 | final List dtos = toButtonDtoList(allowedButtons); 113 | Collections.sort(dtos); 114 | return ok(dtos, APPLICATION_JSON).build(); 115 | } 116 | 117 | @GET 118 | @Path("/projectKey/{projectKey}/repositorySlug/{repositorySlug}") 119 | @Produces(APPLICATION_JSON) 120 | public Response get( 121 | @PathParam("projectKey") String projectKey, 122 | @PathParam("repositorySlug") String repositorySlug) { 123 | final List buttons = settingsService.getButtons(projectKey, repositorySlug); 124 | final Iterable allowedButtons = userCheckService.filterAdminAllowed(buttons); 125 | final List dtos = toButtonDtoList(allowedButtons); 126 | Collections.sort(dtos); 127 | return ok(dtos, APPLICATION_JSON).build(); 128 | } 129 | 130 | @GET 131 | @Path("{uuid}") 132 | @Produces(APPLICATION_JSON) 133 | public Response get(@PathParam("uuid") UUID uuid) { 134 | final PrnfbButton button = settingsService.getButton(uuid); 135 | final USER_LEVEL adminRestriction = 136 | settingsService.getPrnfbSettingsData().getAdminRestriction(); 137 | if (!userCheckService.isAdminAllowed(button, adminRestriction)) { 138 | return status(UNAUTHORIZED).build(); 139 | } 140 | final ButtonDTO dto = toButtonDto(button); 141 | return ok(dto, APPLICATION_JSON).build(); 142 | } 143 | 144 | @GET 145 | @Path("/repository/{repositoryId}/pullrequest/{pullRequestId}") 146 | @Produces(APPLICATION_JSON) 147 | public Response get( 148 | @PathParam("repositoryId") Integer repositoryId, 149 | @PathParam("pullRequestId") Long pullRequestId) { 150 | final List buttons = buttonsService.getButtons(repositoryId, pullRequestId); 151 | final List dtos = toButtonDtoList(buttons); 152 | Collections.sort(dtos); 153 | for (final ButtonDTO dto : dtos) { 154 | renderButtonDtoList(repositoryId, pullRequestId, dto); 155 | } 156 | return ok(dtos, APPLICATION_JSON).build(); 157 | } 158 | 159 | @POST 160 | @Path("{uuid}/press/repository/{repositoryId}/pullrequest/{pullRequestId}") 161 | @XsrfProtectionExcluded 162 | @Produces(APPLICATION_JSON) 163 | public Response press( 164 | @Context HttpServletRequest request, 165 | @PathParam("repositoryId") Integer repositoryId, 166 | @PathParam("pullRequestId") Long pullRequestId, 167 | @PathParam("uuid") final UUID buttionUuid) { 168 | final List buttons = buttonsService.getButtons(repositoryId, pullRequestId); 169 | final Optional button = 170 | Iterables.tryFind(buttons, (b) -> b.getUuid().equals(buttionUuid)); 171 | if (!button.isPresent()) { 172 | return status(NOT_FOUND).build(); 173 | } 174 | final String formData = request.getParameter("form"); 175 | final List results = 176 | buttonsService.handlePressed(repositoryId, pullRequestId, buttionUuid, formData); 177 | 178 | final ButtonPressDTO dto = toTriggerResultDto(button.get(), results); 179 | return ok(dto, APPLICATION_JSON).build(); 180 | } 181 | 182 | private void renderButtonDtoList(Integer repositoryId, Long pullRequestId, ButtonDTO dto) { 183 | final PrnfbRendererWrapper renderer = 184 | buttonsService.getRenderer(repositoryId, pullRequestId, dto.getUuid()); 185 | 186 | final List buttonFormDtoList = dto.getButtonFormList(); 187 | if (buttonFormDtoList != null) { 188 | for (final ButtonFormElementDTO buttonFormElementDto : buttonFormDtoList) { 189 | final String defaultValue = buttonFormElementDto.getDefaultValue(); 190 | if (!isNullOrEmpty(defaultValue)) { 191 | final String defaultValueRendered = renderer.render(defaultValue, ENCODE_FOR.NONE); 192 | buttonFormElementDto.setDefaultValue(defaultValueRendered); 193 | } 194 | } 195 | dto.setButtonFormList(buttonFormDtoList); 196 | } 197 | 198 | final String redirectUrl = dto.getRedirectUrl(); 199 | if (!isNullOrEmpty(redirectUrl)) { 200 | final String redirectUrlRendered = renderer.render(redirectUrl, ENCODE_FOR.HTML); 201 | dto.setRedirectUrl(redirectUrlRendered); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/GlobalAdminServlet.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation; 2 | 3 | import static com.google.common.base.Optional.absent; 4 | import static com.google.common.base.Preconditions.checkNotNull; 5 | import static com.google.common.base.Throwables.propagate; 6 | import static com.google.common.collect.ImmutableMap.of; 7 | import static com.google.common.collect.Maps.newHashMap; 8 | 9 | import com.atlassian.bitbucket.project.Project; 10 | import com.atlassian.bitbucket.project.ProjectService; 11 | import com.atlassian.bitbucket.repository.Repository; 12 | import com.atlassian.bitbucket.repository.RepositoryService; 13 | import com.atlassian.sal.api.auth.LoginUriProvider; 14 | import com.atlassian.sal.api.user.UserManager; 15 | import com.atlassian.sal.api.user.UserProfile; 16 | import com.atlassian.templaterenderer.TemplateRenderer; 17 | import com.google.common.annotations.VisibleForTesting; 18 | import com.google.common.base.Optional; 19 | import java.net.URI; 20 | import java.util.Map; 21 | import javax.servlet.http.HttpServlet; 22 | import javax.servlet.http.HttpServletRequest; 23 | import javax.servlet.http.HttpServletResponse; 24 | import se.bjurr.prnfb.service.UserCheckService; 25 | 26 | public class GlobalAdminServlet extends HttpServlet { 27 | private static final long serialVersionUID = 3846987953228399693L; 28 | private final LoginUriProvider loginUriProvider; 29 | private final TemplateRenderer renderer; 30 | private final RepositoryService repositoryService; 31 | private final ProjectService projectService; 32 | private final UserCheckService userCheckService; 33 | private final UserManager userManager; 34 | 35 | public GlobalAdminServlet( 36 | UserManager userManager, 37 | LoginUriProvider loginUriProvider, 38 | TemplateRenderer renderer, 39 | RepositoryService repositoryService, 40 | UserCheckService userCheckService, 41 | ProjectService projectService) { 42 | this.userManager = userManager; 43 | this.loginUriProvider = loginUriProvider; 44 | this.renderer = renderer; 45 | this.repositoryService = repositoryService; 46 | this.userCheckService = userCheckService; 47 | this.projectService = projectService; 48 | } 49 | 50 | @Override 51 | public void doGet(HttpServletRequest request, HttpServletResponse response) { 52 | try { 53 | UserProfile user = this.userManager.getRemoteUser(request); 54 | if (user == null) { 55 | response.sendRedirect(this.loginUriProvider.getLoginUri(getUri(request)).toASCIIString()); 56 | return; 57 | } 58 | 59 | String projectKey = null; 60 | String repositorySlug = null; 61 | 62 | final Optional repository = getRepository(request.getPathInfo()); 63 | if (repository.isPresent()) { 64 | projectKey = repository.get().getProject().getKey(); 65 | repositorySlug = repository.get().getSlug(); 66 | } 67 | 68 | final Optional project = getProject(request.getPathInfo()); 69 | if (project.isPresent()) { 70 | projectKey = project.get().getKey(); 71 | repositorySlug = null; 72 | } 73 | 74 | boolean isAdmin = 75 | this.userCheckService.isAdmin(user.getUserKey(), projectKey, repositorySlug); 76 | boolean isSystemAdmin = this.userCheckService.isSystemAdmin(user.getUserKey()); 77 | 78 | Map context = newHashMap(); 79 | if (repository.isPresent()) { 80 | context = 81 | of( // 82 | "repository", 83 | repository.get(), // 84 | "isAdmin", 85 | isAdmin, // 86 | "isSystemAdmin", 87 | isSystemAdmin); 88 | } else if (project.isPresent()) { 89 | context = 90 | of( // 91 | "project", 92 | project.get(), // 93 | "isAdmin", 94 | isAdmin, // 95 | "isSystemAdmin", 96 | isSystemAdmin); 97 | } else { 98 | context = 99 | of( // 100 | "isAdmin", isAdmin, // 101 | "isSystemAdmin", isSystemAdmin); 102 | } 103 | 104 | response.setContentType("text/html;charset=UTF-8"); 105 | this.renderer.render( // 106 | "admin.vm", // 107 | context, // 108 | response.getWriter()); 109 | } catch (Exception e) { 110 | propagate(e); 111 | } 112 | } 113 | 114 | private URI getUri(HttpServletRequest request) { 115 | StringBuffer builder = request.getRequestURL(); 116 | if (request.getQueryString() != null) { 117 | builder.append("?"); 118 | builder.append(request.getQueryString()); 119 | } 120 | return URI.create(builder.toString()); 121 | } 122 | 123 | @VisibleForTesting 124 | Optional getProject(String pathInfo) { 125 | Optional componentsOpt = getComponents(pathInfo); 126 | if (!componentsOpt.isPresent() || componentsOpt.get().length != 1) { 127 | return absent(); 128 | } 129 | String[] components = componentsOpt.get(); 130 | String projectKey = components[0]; 131 | Project project = projectService.getByKey(projectKey); 132 | return Optional.of(project); 133 | } 134 | 135 | @VisibleForTesting 136 | Optional getRepository(String pathInfo) { 137 | Optional componentsOpt = getComponents(pathInfo); 138 | if (!componentsOpt.isPresent() || componentsOpt.get().length != 2) { 139 | return absent(); 140 | } 141 | String[] components = componentsOpt.get(); 142 | String project = components[0]; 143 | String repoSlug = components[1]; 144 | final Repository repository = 145 | checkNotNull( 146 | this.repositoryService.getBySlug(project, repoSlug), // 147 | "Did not find " + project + " " + repoSlug); 148 | return Optional.of(repository); 149 | } 150 | 151 | private Optional getComponents(String pathInfo) { 152 | if (pathInfo == null || pathInfo.isEmpty()) { 153 | return absent(); 154 | } 155 | int indexOf = pathInfo.indexOf("prnfb/admin/"); 156 | if (indexOf == -1) { 157 | return absent(); 158 | } 159 | String root = pathInfo.substring(indexOf + "prnfb/admin/".length()); 160 | if (root.isEmpty()) { 161 | return absent(); 162 | } 163 | String[] split = root.split("/"); 164 | return Optional.of(split); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/NotificationServlet.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation; 2 | 3 | import static com.google.common.base.Throwables.propagate; 4 | import static javax.ws.rs.core.MediaType.APPLICATION_JSON; 5 | import static javax.ws.rs.core.Response.Status.OK; 6 | import static javax.ws.rs.core.Response.Status.UNAUTHORIZED; 7 | import static javax.ws.rs.core.Response.ok; 8 | import static javax.ws.rs.core.Response.status; 9 | import static se.bjurr.prnfb.transformer.NotificationTransformer.toNotificationDto; 10 | import static se.bjurr.prnfb.transformer.NotificationTransformer.toNotificationDtoList; 11 | import static se.bjurr.prnfb.transformer.NotificationTransformer.toPrnfbNotification; 12 | 13 | import com.atlassian.annotations.security.XsrfProtectionExcluded; 14 | import java.util.Collections; 15 | import java.util.List; 16 | import java.util.UUID; 17 | import javax.ws.rs.Consumes; 18 | import javax.ws.rs.DELETE; 19 | import javax.ws.rs.GET; 20 | import javax.ws.rs.POST; 21 | import javax.ws.rs.Path; 22 | import javax.ws.rs.PathParam; 23 | import javax.ws.rs.Produces; 24 | import javax.ws.rs.core.Response; 25 | import se.bjurr.prnfb.presentation.dto.NotificationDTO; 26 | import se.bjurr.prnfb.service.SettingsService; 27 | import se.bjurr.prnfb.service.UserCheckService; 28 | import se.bjurr.prnfb.settings.PrnfbNotification; 29 | import se.bjurr.prnfb.settings.USER_LEVEL; 30 | 31 | @Path("/settings/notifications") 32 | public class NotificationServlet { 33 | private final SettingsService settingsService; 34 | private final UserCheckService userCheckService; 35 | 36 | public NotificationServlet(SettingsService settingsService, UserCheckService userCheckService) { 37 | this.settingsService = settingsService; 38 | this.userCheckService = userCheckService; 39 | } 40 | 41 | @POST 42 | @XsrfProtectionExcluded 43 | @Consumes(APPLICATION_JSON) 44 | @Produces(APPLICATION_JSON) 45 | public Response create(NotificationDTO notificationDto) { 46 | final USER_LEVEL adminRestriction = 47 | settingsService.getPrnfbSettingsData().getAdminRestriction(); 48 | if (!this.userCheckService.isAdminAllowed(notificationDto, adminRestriction)) { 49 | return status(UNAUTHORIZED).build(); 50 | } 51 | try { 52 | final PrnfbNotification prnfbNotification = toPrnfbNotification(notificationDto); 53 | final PrnfbNotification created = 54 | this.settingsService.addOrUpdateNotification(prnfbNotification); 55 | final NotificationDTO createdDto = toNotificationDto(created); 56 | return status(OK) // 57 | .entity(createdDto) // 58 | .build(); 59 | } catch (final Exception e) { 60 | throw propagate(e); 61 | } 62 | } 63 | 64 | @DELETE 65 | @Path("{uuid}") 66 | @XsrfProtectionExcluded 67 | @Produces(APPLICATION_JSON) 68 | public Response delete(@PathParam("uuid") UUID notification) { 69 | final PrnfbNotification notificationDto = this.settingsService.getNotification(notification); 70 | final USER_LEVEL adminRestriction = 71 | settingsService.getPrnfbSettingsData().getAdminRestriction(); 72 | if (!this.userCheckService.isAdminAllowed(notificationDto, adminRestriction)) { 73 | return status(UNAUTHORIZED).build(); 74 | } 75 | this.settingsService.deleteNotification(notification); 76 | return status(OK).build(); 77 | } 78 | 79 | @GET 80 | @Produces(APPLICATION_JSON) 81 | public Response get() { 82 | final List notifications = this.settingsService.getNotifications(); 83 | final Iterable notificationsFiltered = 84 | userCheckService.filterAdminAllowed(notifications); 85 | final List dtos = toNotificationDtoList(notificationsFiltered); 86 | Collections.sort(dtos); 87 | return ok(dtos).build(); 88 | } 89 | 90 | @GET 91 | @Path("/projectKey/{projectKey}") 92 | @Produces(APPLICATION_JSON) 93 | public Response get(@PathParam("projectKey") String projectKey) { 94 | final List notifications = this.settingsService.getNotifications(projectKey); 95 | final Iterable notificationsFiltered = 96 | userCheckService.filterAdminAllowed(notifications); 97 | final List dtos = toNotificationDtoList(notificationsFiltered); 98 | Collections.sort(dtos); 99 | return ok(dtos).build(); 100 | } 101 | 102 | @GET 103 | @Path("/projectKey/{projectKey}/repositorySlug/{repositorySlug}") 104 | @Produces(APPLICATION_JSON) 105 | public Response get( 106 | @PathParam("projectKey") String projectKey, 107 | @PathParam("repositorySlug") String repositorySlug) { 108 | final List notifications = 109 | this.settingsService.getNotifications(projectKey, repositorySlug); 110 | final Iterable notificationsFiltered = 111 | userCheckService.filterAdminAllowed(notifications); 112 | final List dtos = toNotificationDtoList(notificationsFiltered); 113 | Collections.sort(dtos); 114 | return ok(dtos).build(); 115 | } 116 | 117 | @GET 118 | @Path("{uuid}") 119 | @Produces(APPLICATION_JSON) 120 | public Response get(@PathParam("uuid") UUID notificationUuid) { 121 | final PrnfbNotification notification = this.settingsService.getNotification(notificationUuid); 122 | final USER_LEVEL adminRestriction = 123 | settingsService.getPrnfbSettingsData().getAdminRestriction(); 124 | if (!this.userCheckService.isAdminAllowed(notification, adminRestriction)) { 125 | return status(UNAUTHORIZED).build(); 126 | } 127 | final NotificationDTO dto = toNotificationDto(notification); 128 | return ok(dto).build(); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/SettingsDataServlet.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation; 2 | 3 | import static javax.ws.rs.core.MediaType.APPLICATION_JSON; 4 | import static javax.ws.rs.core.Response.Status.UNAUTHORIZED; 5 | import static javax.ws.rs.core.Response.noContent; 6 | import static javax.ws.rs.core.Response.ok; 7 | import static javax.ws.rs.core.Response.status; 8 | import static se.bjurr.prnfb.transformer.SettingsTransformer.toDto; 9 | import static se.bjurr.prnfb.transformer.SettingsTransformer.toPrnfbSettingsData; 10 | 11 | import com.atlassian.annotations.security.XsrfProtectionExcluded; 12 | import com.google.common.base.Optional; 13 | import javax.ws.rs.Consumes; 14 | import javax.ws.rs.GET; 15 | import javax.ws.rs.POST; 16 | import javax.ws.rs.Path; 17 | import javax.ws.rs.Produces; 18 | import javax.ws.rs.core.Response; 19 | import se.bjurr.prnfb.presentation.dto.SettingsDataDTO; 20 | import se.bjurr.prnfb.service.SettingsService; 21 | import se.bjurr.prnfb.service.UserCheckService; 22 | import se.bjurr.prnfb.settings.PrnfbSettingsData; 23 | import se.bjurr.prnfb.settings.Restricted; 24 | import se.bjurr.prnfb.settings.USER_LEVEL; 25 | 26 | @Path("/settings") 27 | public class SettingsDataServlet { 28 | private final SettingsService settingsService; 29 | private final UserCheckService userCheckService; 30 | 31 | public SettingsDataServlet(UserCheckService userCheckService, SettingsService settingsService) { 32 | this.userCheckService = userCheckService; 33 | this.settingsService = settingsService; 34 | } 35 | 36 | @GET 37 | @Produces(APPLICATION_JSON) 38 | public Response get() { 39 | if (!this.userCheckService.isViewAllowed()) { 40 | return status(UNAUTHORIZED).build(); 41 | } 42 | 43 | final PrnfbSettingsData settingsData = this.settingsService.getPrnfbSettingsData(); 44 | final SettingsDataDTO settingsDataDto = toDto(settingsData); 45 | 46 | return ok(settingsDataDto).build(); 47 | } 48 | 49 | @POST 50 | @XsrfProtectionExcluded 51 | @Consumes(APPLICATION_JSON) 52 | @Produces(APPLICATION_JSON) 53 | public Response post(SettingsDataDTO settingsDataDto) { 54 | final USER_LEVEL adminRestriction = 55 | settingsService.getPrnfbSettingsData().getAdminRestriction(); 56 | if (!this.userCheckService.isAdminAllowed( 57 | new Restricted() { 58 | @Override 59 | public Optional getRepositorySlug() { 60 | return Optional.absent(); 61 | } 62 | 63 | @Override 64 | public Optional getProjectKey() { 65 | return Optional.absent(); 66 | } 67 | }, 68 | adminRestriction)) { 69 | return status(UNAUTHORIZED).build(); 70 | } 71 | 72 | final PrnfbSettingsData prnfbSettingsData = toPrnfbSettingsData(settingsDataDto); 73 | this.settingsService.setPrnfbSettingsData(prnfbSettingsData); 74 | 75 | return noContent().build(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/dto/ButtonDTO.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation.dto; 2 | 3 | import static javax.xml.bind.annotation.XmlAccessType.FIELD; 4 | 5 | import com.google.common.base.Optional; 6 | import com.google.gson.reflect.TypeToken; 7 | import java.lang.reflect.Type; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.UUID; 11 | import javax.xml.bind.annotation.XmlAccessorType; 12 | import javax.xml.bind.annotation.XmlRootElement; 13 | import se.bjurr.prnfb.settings.Restricted; 14 | import se.bjurr.prnfb.settings.USER_LEVEL; 15 | 16 | @XmlRootElement 17 | @XmlAccessorType(FIELD) 18 | public class ButtonDTO implements Comparable, Restricted { 19 | public static Type BUTTON_FORM_LIST_DTO_TYPE = 20 | new TypeToken>() {}.getType(); 21 | 22 | private List buttonFormList; 23 | /** 24 | * Makes it easier to implement GUI. JSON String representation of {@link #buttonFormList}. If 25 | * {@link #buttonFormList} is not defined and {@link #buttonFormListString} is defined, then that 26 | * is parsed to {@link #buttonFormList}. 27 | */ 28 | private String buttonFormListString; 29 | 30 | private ON_OR_OFF confirmation; 31 | private String name; 32 | private String projectKey; 33 | private String repositorySlug; 34 | private USER_LEVEL userLevel; 35 | private UUID uuid; 36 | private String confirmationText; 37 | private String redirectUrl; 38 | 39 | public void setConfirmationText(String confirmationText) { 40 | this.confirmationText = confirmationText; 41 | } 42 | 43 | public String getConfirmationText() { 44 | return confirmationText; 45 | } 46 | 47 | @Override 48 | public int compareTo(ButtonDTO o) { 49 | return this.name.compareTo(o.name); 50 | } 51 | 52 | @Override 53 | public boolean equals(Object obj) { 54 | if (this == obj) { 55 | return true; 56 | } 57 | if (obj == null) { 58 | return false; 59 | } 60 | if (getClass() != obj.getClass()) { 61 | return false; 62 | } 63 | ButtonDTO other = (ButtonDTO) obj; 64 | if (buttonFormList == null) { 65 | if (other.buttonFormList != null) { 66 | return false; 67 | } 68 | } else if (!buttonFormList.equals(other.buttonFormList)) { 69 | return false; 70 | } 71 | if (buttonFormListString == null) { 72 | if (other.buttonFormListString != null) { 73 | return false; 74 | } 75 | } else if (!buttonFormListString.equals(other.buttonFormListString)) { 76 | return false; 77 | } 78 | if (confirmation != other.confirmation) { 79 | return false; 80 | } 81 | if (confirmationText == null) { 82 | if (other.confirmationText != null) { 83 | return false; 84 | } 85 | } else if (!confirmationText.equals(other.confirmationText)) { 86 | return false; 87 | } 88 | if (name == null) { 89 | if (other.name != null) { 90 | return false; 91 | } 92 | } else if (!name.equals(other.name)) { 93 | return false; 94 | } 95 | if (projectKey == null) { 96 | if (other.projectKey != null) { 97 | return false; 98 | } 99 | } else if (!projectKey.equals(other.projectKey)) { 100 | return false; 101 | } 102 | if (repositorySlug == null) { 103 | if (other.repositorySlug != null) { 104 | return false; 105 | } 106 | } else if (!repositorySlug.equals(other.repositorySlug)) { 107 | return false; 108 | } 109 | if (userLevel != other.userLevel) { 110 | return false; 111 | } 112 | if (uuid == null) { 113 | if (other.uuid != null) { 114 | return false; 115 | } 116 | } else if (!uuid.equals(other.uuid)) { 117 | return false; 118 | } 119 | if (redirectUrl == null) { 120 | if (other.redirectUrl != null) { 121 | return false; 122 | } 123 | } else if (!redirectUrl.equals(other.redirectUrl)) { 124 | return false; 125 | } 126 | return true; 127 | } 128 | 129 | public List getButtonFormList() { 130 | return buttonFormList; 131 | } 132 | 133 | public ON_OR_OFF getConfirmation() { 134 | return this.confirmation; 135 | } 136 | 137 | public String getName() { 138 | return this.name; 139 | } 140 | 141 | @Override 142 | public Optional getProjectKey() { 143 | return Optional.fromNullable(this.projectKey); 144 | } 145 | 146 | @Override 147 | public Optional getRepositorySlug() { 148 | return Optional.fromNullable(this.repositorySlug); 149 | } 150 | 151 | public USER_LEVEL getUserLevel() { 152 | return this.userLevel; 153 | } 154 | 155 | public UUID getUuid() { 156 | return this.uuid; 157 | } 158 | 159 | public UUID getUUID() { 160 | return this.uuid; 161 | } 162 | 163 | public String getRedirectUrl() { 164 | return this.redirectUrl; 165 | } 166 | 167 | @Override 168 | public int hashCode() { 169 | final int prime = 31; 170 | int result = 1; 171 | result = prime * result + (buttonFormList == null ? 0 : buttonFormList.hashCode()); 172 | result = prime * result + (buttonFormListString == null ? 0 : buttonFormListString.hashCode()); 173 | result = prime * result + (confirmation == null ? 0 : confirmation.hashCode()); 174 | result = prime * result + (confirmationText == null ? 0 : confirmationText.hashCode()); 175 | result = prime * result + (name == null ? 0 : name.hashCode()); 176 | result = prime * result + (projectKey == null ? 0 : projectKey.hashCode()); 177 | result = prime * result + (repositorySlug == null ? 0 : repositorySlug.hashCode()); 178 | result = prime * result + (userLevel == null ? 0 : userLevel.hashCode()); 179 | result = prime * result + (uuid == null ? 0 : uuid.hashCode()); 180 | result = prime * result + (redirectUrl == null ? 0 : redirectUrl.hashCode()); 181 | return result; 182 | } 183 | 184 | public void setButtonFormListString(String buttonFormDtoListString) { 185 | this.buttonFormListString = buttonFormDtoListString; 186 | } 187 | 188 | public String getButtonFormListString() { 189 | return buttonFormListString; 190 | } 191 | 192 | public void setButtonFormList(List buttonFormList) { 193 | this.buttonFormList = buttonFormList; 194 | } 195 | 196 | public void setConfirmation(ON_OR_OFF confirmation) { 197 | this.confirmation = confirmation; 198 | } 199 | 200 | public void setName(String name) { 201 | this.name = name; 202 | } 203 | 204 | public void setProjectKey(String projectKey) { 205 | this.projectKey = projectKey; 206 | } 207 | 208 | public void setRepositorySlug(String repositorySlug) { 209 | this.repositorySlug = repositorySlug; 210 | } 211 | 212 | public void setUserLevel(USER_LEVEL userLevel) { 213 | this.userLevel = userLevel; 214 | } 215 | 216 | public void setUuid(UUID uuid) { 217 | this.uuid = uuid; 218 | } 219 | 220 | public void setRedirectUrl(String redirectUrl) { 221 | this.redirectUrl = redirectUrl; 222 | } 223 | 224 | @Override 225 | public String toString() { 226 | return "ButtonDTO [buttonFormList=" 227 | + buttonFormList 228 | + ", buttonFormListString=" 229 | + buttonFormListString 230 | + ", confirmation=" 231 | + confirmation 232 | + ", name=" 233 | + name 234 | + ", projectKey=" 235 | + projectKey 236 | + ", repositorySlug=" 237 | + repositorySlug 238 | + ", userLevel=" 239 | + userLevel 240 | + ", uuid=" 241 | + uuid 242 | + ", confirmationText=" 243 | + confirmationText 244 | + ", redirectUrl=" 245 | + redirectUrl 246 | + "]"; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/dto/ButtonFormElementDTO.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation.dto; 2 | 3 | import static javax.xml.bind.annotation.XmlAccessType.FIELD; 4 | 5 | import java.util.List; 6 | import javax.xml.bind.annotation.XmlAccessorType; 7 | import javax.xml.bind.annotation.XmlRootElement; 8 | 9 | @XmlRootElement 10 | @XmlAccessorType(FIELD) 11 | public class ButtonFormElementDTO { 12 | private String defaultValue; 13 | private String description; 14 | private String label; 15 | private String name; 16 | private List buttonFormElementOptionList; 17 | private boolean required; 18 | private ButtonFormType type; 19 | 20 | @Override 21 | public boolean equals(Object obj) { 22 | if (this == obj) { 23 | return true; 24 | } 25 | if (obj == null) { 26 | return false; 27 | } 28 | if (getClass() != obj.getClass()) { 29 | return false; 30 | } 31 | ButtonFormElementDTO other = (ButtonFormElementDTO) obj; 32 | if (defaultValue == null) { 33 | if (other.defaultValue != null) { 34 | return false; 35 | } 36 | } else if (!defaultValue.equals(other.defaultValue)) { 37 | return false; 38 | } 39 | if (description == null) { 40 | if (other.description != null) { 41 | return false; 42 | } 43 | } else if (!description.equals(other.description)) { 44 | return false; 45 | } 46 | if (label == null) { 47 | if (other.label != null) { 48 | return false; 49 | } 50 | } else if (!label.equals(other.label)) { 51 | return false; 52 | } 53 | if (name == null) { 54 | if (other.name != null) { 55 | return false; 56 | } 57 | } else if (!name.equals(other.name)) { 58 | return false; 59 | } 60 | if (buttonFormElementOptionList == null) { 61 | if (other.buttonFormElementOptionList != null) { 62 | return false; 63 | } 64 | } else if (!buttonFormElementOptionList.equals(other.buttonFormElementOptionList)) { 65 | return false; 66 | } 67 | if (required != other.required) { 68 | return false; 69 | } 70 | if (type != other.type) { 71 | return false; 72 | } 73 | return true; 74 | } 75 | 76 | public String getDefaultValue() { 77 | return defaultValue; 78 | } 79 | 80 | public String getDescription() { 81 | return description; 82 | } 83 | 84 | public String getLabel() { 85 | return label; 86 | } 87 | 88 | public String getName() { 89 | return name; 90 | } 91 | 92 | public List getButtonFormElementOptionList() { 93 | return buttonFormElementOptionList; 94 | } 95 | 96 | public boolean getRequired() { 97 | return required; 98 | } 99 | 100 | public ButtonFormType getType() { 101 | return type; 102 | } 103 | 104 | @Override 105 | public int hashCode() { 106 | final int prime = 31; 107 | int result = 1; 108 | result = prime * result + (defaultValue == null ? 0 : defaultValue.hashCode()); 109 | result = prime * result + (description == null ? 0 : description.hashCode()); 110 | result = prime * result + (label == null ? 0 : label.hashCode()); 111 | result = prime * result + (name == null ? 0 : name.hashCode()); 112 | result = 113 | prime * result 114 | + (buttonFormElementOptionList == null ? 0 : buttonFormElementOptionList.hashCode()); 115 | result = prime * result + (required ? 1231 : 1237); 116 | result = prime * result + (type == null ? 0 : type.hashCode()); 117 | return result; 118 | } 119 | 120 | public void setDefaultValue(String defaultValue) { 121 | this.defaultValue = defaultValue; 122 | } 123 | 124 | public void setDescription(String description) { 125 | this.description = description; 126 | } 127 | 128 | public void setLabel(String label) { 129 | this.label = label; 130 | } 131 | 132 | public void setName(String name) { 133 | this.name = name; 134 | } 135 | 136 | public void setButtonFormElementOptionList(List options) { 137 | this.buttonFormElementOptionList = options; 138 | } 139 | 140 | public void setRequired(boolean required) { 141 | this.required = required; 142 | } 143 | 144 | public void setType(ButtonFormType type) { 145 | this.type = type; 146 | } 147 | 148 | @Override 149 | public String toString() { 150 | return "ButtonFormDTO [options=" 151 | + buttonFormElementOptionList 152 | + ", name=" 153 | + name 154 | + ", label=" 155 | + label 156 | + ", defaultValue=" 157 | + defaultValue 158 | + ", type=" 159 | + type 160 | + ", required=" 161 | + required 162 | + ", description=" 163 | + description 164 | + "]"; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/dto/ButtonFormElementOptionDTO.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation.dto; 2 | 3 | import static javax.xml.bind.annotation.XmlAccessType.FIELD; 4 | 5 | import javax.xml.bind.annotation.XmlAccessorType; 6 | import javax.xml.bind.annotation.XmlRootElement; 7 | 8 | /** @see ButtonFormElementDTO */ 9 | @XmlRootElement 10 | @XmlAccessorType(FIELD) 11 | public class ButtonFormElementOptionDTO { 12 | private String label; 13 | private String name; 14 | private Boolean defaultValue; 15 | 16 | public void setDefaultValue(Boolean defaultValue) { 17 | this.defaultValue = defaultValue; 18 | } 19 | 20 | public void setLabel(String label) { 21 | this.label = label; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public Boolean getDefaultValue() { 29 | return defaultValue; 30 | } 31 | 32 | public String getLabel() { 33 | return label; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "ButtonFormOptionDTO [label=" 43 | + label 44 | + ", name=" 45 | + name 46 | + ", defaultValue=" 47 | + defaultValue 48 | + "]"; 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | final int prime = 31; 54 | int result = 1; 55 | result = prime * result + (defaultValue == null ? 0 : defaultValue.hashCode()); 56 | result = prime * result + (label == null ? 0 : label.hashCode()); 57 | result = prime * result + (name == null ? 0 : name.hashCode()); 58 | return result; 59 | } 60 | 61 | @Override 62 | public boolean equals(Object obj) { 63 | if (this == obj) { 64 | return true; 65 | } 66 | if (obj == null) { 67 | return false; 68 | } 69 | if (getClass() != obj.getClass()) { 70 | return false; 71 | } 72 | ButtonFormElementOptionDTO other = (ButtonFormElementOptionDTO) obj; 73 | if (defaultValue == null) { 74 | if (other.defaultValue != null) { 75 | return false; 76 | } 77 | } else if (!defaultValue.equals(other.defaultValue)) { 78 | return false; 79 | } 80 | if (label == null) { 81 | if (other.label != null) { 82 | return false; 83 | } 84 | } else if (!label.equals(other.label)) { 85 | return false; 86 | } 87 | if (name == null) { 88 | if (other.name != null) { 89 | return false; 90 | } 91 | } else if (!name.equals(other.name)) { 92 | return false; 93 | } 94 | return true; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/dto/ButtonFormType.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation.dto; 2 | 3 | public enum ButtonFormType { 4 | checkbox, 5 | input, 6 | radio, 7 | textarea 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/dto/ButtonPressDTO.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation.dto; 2 | 3 | import static javax.xml.bind.annotation.XmlAccessType.FIELD; 4 | 5 | import java.util.List; 6 | import javax.xml.bind.annotation.XmlAccessorType; 7 | import javax.xml.bind.annotation.XmlRootElement; 8 | 9 | @XmlRootElement 10 | @XmlAccessorType(FIELD) 11 | public class ButtonPressDTO { 12 | private final ON_OR_OFF confirmation; 13 | private final List notificationResponses; 14 | 15 | public ButtonPressDTO( 16 | ON_OR_OFF confirmation, List notificationResponses) { 17 | this.confirmation = confirmation; 18 | this.notificationResponses = notificationResponses; 19 | } 20 | 21 | @Override 22 | public boolean equals(Object obj) { 23 | if (this == obj) { 24 | return true; 25 | } 26 | if (obj == null) { 27 | return false; 28 | } 29 | if (getClass() != obj.getClass()) { 30 | return false; 31 | } 32 | ButtonPressDTO other = (ButtonPressDTO) obj; 33 | if (this.confirmation != other.confirmation) { 34 | return false; 35 | } 36 | if (this.notificationResponses == null) { 37 | if (other.notificationResponses != null) { 38 | return false; 39 | } 40 | } else if (!this.notificationResponses.equals(other.notificationResponses)) { 41 | return false; 42 | } 43 | return true; 44 | } 45 | 46 | public ON_OR_OFF getConfirmation() { 47 | return this.confirmation; 48 | } 49 | 50 | public List getNotificationResponses() { 51 | return this.notificationResponses; 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | final int prime = 31; 57 | int result = 1; 58 | result = prime * result + ((this.confirmation == null) ? 0 : this.confirmation.hashCode()); 59 | result = 60 | prime * result 61 | + ((this.notificationResponses == null) ? 0 : this.notificationResponses.hashCode()); 62 | return result; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "ButtonPressDTO [confirmation=" 68 | + this.confirmation 69 | + ", notificationResponses=" 70 | + this.notificationResponses 71 | + "]"; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/dto/HeaderDTO.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation.dto; 2 | 3 | import static javax.xml.bind.annotation.XmlAccessType.FIELD; 4 | 5 | import javax.xml.bind.annotation.XmlAccessorType; 6 | import javax.xml.bind.annotation.XmlRootElement; 7 | 8 | @XmlRootElement 9 | @XmlAccessorType(FIELD) 10 | public class HeaderDTO { 11 | 12 | private String name; 13 | private String value; 14 | 15 | @Override 16 | public boolean equals(Object obj) { 17 | if (this == obj) { 18 | return true; 19 | } 20 | if (obj == null) { 21 | return false; 22 | } 23 | if (getClass() != obj.getClass()) { 24 | return false; 25 | } 26 | HeaderDTO other = (HeaderDTO) obj; 27 | if (this.name == null) { 28 | if (other.name != null) { 29 | return false; 30 | } 31 | } else if (!this.name.equals(other.name)) { 32 | return false; 33 | } 34 | if (this.value == null) { 35 | if (other.value != null) { 36 | return false; 37 | } 38 | } else if (!this.value.equals(other.value)) { 39 | return false; 40 | } 41 | return true; 42 | } 43 | 44 | public String getName() { 45 | return this.name; 46 | } 47 | 48 | public String getValue() { 49 | return this.value; 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | final int prime = 31; 55 | int result = 1; 56 | result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); 57 | result = prime * result + ((this.value == null) ? 0 : this.value.hashCode()); 58 | return result; 59 | } 60 | 61 | public void setName(String name) { 62 | this.name = name; 63 | } 64 | 65 | public void setValue(String value) { 66 | this.value = value; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "HeaderDTO [name=" + this.name + ", value=" + this.value + "]"; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/dto/NotificationResponseDTO.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation.dto; 2 | 3 | import static javax.xml.bind.annotation.XmlAccessType.FIELD; 4 | 5 | import java.net.URI; 6 | import java.util.UUID; 7 | import javax.xml.bind.annotation.XmlAccessorType; 8 | import javax.xml.bind.annotation.XmlRootElement; 9 | 10 | @XmlRootElement 11 | @XmlAccessorType(FIELD) 12 | public class NotificationResponseDTO implements Comparable { 13 | private final String content; 14 | private final UUID notification; 15 | private final String notificationName; 16 | private final int status; 17 | private final URI uri; 18 | 19 | public NotificationResponseDTO( 20 | URI uri, String content, int status, UUID notification, String notificationName) { 21 | this.content = content; 22 | this.status = status; 23 | this.notification = notification; 24 | this.notificationName = notificationName; 25 | this.uri = uri; 26 | } 27 | 28 | @Override 29 | public int compareTo(NotificationResponseDTO o) { 30 | return this.notificationName.compareTo(o.notificationName); 31 | } 32 | 33 | @Override 34 | public boolean equals(Object obj) { 35 | if (this == obj) { 36 | return true; 37 | } 38 | if (obj == null) { 39 | return false; 40 | } 41 | if (getClass() != obj.getClass()) { 42 | return false; 43 | } 44 | NotificationResponseDTO other = (NotificationResponseDTO) obj; 45 | if (this.content == null) { 46 | if (other.content != null) { 47 | return false; 48 | } 49 | } else if (!this.content.equals(other.content)) { 50 | return false; 51 | } 52 | if (this.notification == null) { 53 | if (other.notification != null) { 54 | return false; 55 | } 56 | } else if (!this.notification.equals(other.notification)) { 57 | return false; 58 | } 59 | if (this.notificationName == null) { 60 | if (other.notificationName != null) { 61 | return false; 62 | } 63 | } else if (!this.notificationName.equals(other.notificationName)) { 64 | return false; 65 | } 66 | if (this.status != other.status) { 67 | return false; 68 | } 69 | if (this.uri == null) { 70 | if (other.uri != null) { 71 | return false; 72 | } 73 | } else if (!this.uri.equals(other.uri)) { 74 | return false; 75 | } 76 | return true; 77 | } 78 | 79 | public URI getUri() { 80 | return this.uri; 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | final int prime = 31; 86 | int result = 1; 87 | result = prime * result + ((this.content == null) ? 0 : this.content.hashCode()); 88 | result = prime * result + ((this.notification == null) ? 0 : this.notification.hashCode()); 89 | result = 90 | prime * result + ((this.notificationName == null) ? 0 : this.notificationName.hashCode()); 91 | result = prime * result + this.status; 92 | result = prime * result + ((this.uri == null) ? 0 : this.uri.hashCode()); 93 | return result; 94 | } 95 | 96 | @Override 97 | public String toString() { 98 | return "NotificationResponseDTO [content=" 99 | + this.content 100 | + ", notification=" 101 | + this.notification 102 | + ", notificationName=" 103 | + this.notificationName 104 | + ", status=" 105 | + this.status 106 | + ", uri=" 107 | + this.uri 108 | + "]"; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/dto/ON_OR_OFF.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation.dto; 2 | 3 | public enum ON_OR_OFF { 4 | on, 5 | off 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/presentation/dto/SettingsDataDTO.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.presentation.dto; 2 | 3 | import static javax.xml.bind.annotation.XmlAccessType.FIELD; 4 | 5 | import javax.xml.bind.annotation.XmlAccessorType; 6 | import javax.xml.bind.annotation.XmlRootElement; 7 | import se.bjurr.prnfb.settings.USER_LEVEL; 8 | 9 | @XmlRootElement 10 | @XmlAccessorType(FIELD) 11 | public class SettingsDataDTO { 12 | 13 | private USER_LEVEL adminRestriction; 14 | private String keyStore; 15 | private String keyStorePassword; 16 | private String keyStoreType; 17 | private boolean shouldAcceptAnyCertificate; 18 | 19 | @Override 20 | public boolean equals(Object obj) { 21 | if (this == obj) { 22 | return true; 23 | } 24 | if (obj == null) { 25 | return false; 26 | } 27 | if (getClass() != obj.getClass()) { 28 | return false; 29 | } 30 | SettingsDataDTO other = (SettingsDataDTO) obj; 31 | if (this.adminRestriction != other.adminRestriction) { 32 | return false; 33 | } 34 | if (this.keyStore == null) { 35 | if (other.keyStore != null) { 36 | return false; 37 | } 38 | } else if (!this.keyStore.equals(other.keyStore)) { 39 | return false; 40 | } 41 | if (this.keyStorePassword == null) { 42 | if (other.keyStorePassword != null) { 43 | return false; 44 | } 45 | } else if (!this.keyStorePassword.equals(other.keyStorePassword)) { 46 | return false; 47 | } 48 | if (this.keyStoreType == null) { 49 | if (other.keyStoreType != null) { 50 | return false; 51 | } 52 | } else if (!this.keyStoreType.equals(other.keyStoreType)) { 53 | return false; 54 | } 55 | if (this.shouldAcceptAnyCertificate != other.shouldAcceptAnyCertificate) { 56 | return false; 57 | } 58 | return true; 59 | } 60 | 61 | public USER_LEVEL getAdminRestriction() { 62 | return this.adminRestriction; 63 | } 64 | 65 | public String getKeyStore() { 66 | return this.keyStore; 67 | } 68 | 69 | public String getKeyStorePassword() { 70 | return this.keyStorePassword; 71 | } 72 | 73 | public String getKeyStoreType() { 74 | return this.keyStoreType; 75 | } 76 | 77 | @Override 78 | public int hashCode() { 79 | final int prime = 31; 80 | int result = 1; 81 | result = 82 | prime * result + ((this.adminRestriction == null) ? 0 : this.adminRestriction.hashCode()); 83 | result = prime * result + ((this.keyStore == null) ? 0 : this.keyStore.hashCode()); 84 | result = 85 | prime * result + ((this.keyStorePassword == null) ? 0 : this.keyStorePassword.hashCode()); 86 | result = prime * result + ((this.keyStoreType == null) ? 0 : this.keyStoreType.hashCode()); 87 | result = prime * result + (this.shouldAcceptAnyCertificate ? 1231 : 1237); 88 | return result; 89 | } 90 | 91 | public boolean isShouldAcceptAnyCertificate() { 92 | return this.shouldAcceptAnyCertificate; 93 | } 94 | 95 | public void setAdminRestriction(USER_LEVEL adminRestriction) { 96 | this.adminRestriction = adminRestriction; 97 | } 98 | 99 | public void setKeyStore(String keyStore) { 100 | this.keyStore = keyStore; 101 | } 102 | 103 | public void setKeyStorePassword(String keyStorePassword) { 104 | this.keyStorePassword = keyStorePassword; 105 | } 106 | 107 | public void setKeyStoreType(String keyStoreType) { 108 | this.keyStoreType = keyStoreType; 109 | } 110 | 111 | public void setShouldAcceptAnyCertificate(boolean shouldAcceptAnyCertificate) { 112 | this.shouldAcceptAnyCertificate = shouldAcceptAnyCertificate; 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | return "SettingsDataDTO [adminRestriction=" 118 | + this.adminRestriction 119 | + ", keyStore=" 120 | + this.keyStore 121 | + ", keyStorePassword=" 122 | + this.keyStorePassword 123 | + ", keyStoreType=" 124 | + this.keyStoreType 125 | + ", shouldAcceptAnyCertificate=" 126 | + this.shouldAcceptAnyCertificate 127 | + "]"; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/service/JsonEscaper.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.service; 2 | 3 | import java.io.StringWriter; 4 | 5 | /** Copied from GSON JsonWriter */ 6 | public class JsonEscaper { 7 | 8 | private static final String[] REPLACEMENT_CHARS; 9 | private static final String[] HTML_SAFE_REPLACEMENT_CHARS; 10 | 11 | static { 12 | REPLACEMENT_CHARS = new String[128]; 13 | for (int i = 0; i <= 0x1f; i++) { 14 | REPLACEMENT_CHARS[i] = String.format("\\u%04x", (int) i); 15 | } 16 | REPLACEMENT_CHARS['"'] = "\\\""; 17 | REPLACEMENT_CHARS['\\'] = "\\\\"; 18 | REPLACEMENT_CHARS['\t'] = "\\t"; 19 | REPLACEMENT_CHARS['\b'] = "\\b"; 20 | REPLACEMENT_CHARS['\n'] = "\\n"; 21 | REPLACEMENT_CHARS['\r'] = "\\r"; 22 | REPLACEMENT_CHARS['\f'] = "\\f"; 23 | HTML_SAFE_REPLACEMENT_CHARS = REPLACEMENT_CHARS.clone(); 24 | HTML_SAFE_REPLACEMENT_CHARS['<'] = "\\u003c"; 25 | HTML_SAFE_REPLACEMENT_CHARS['>'] = "\\u003e"; 26 | HTML_SAFE_REPLACEMENT_CHARS['&'] = "\\u0026"; 27 | HTML_SAFE_REPLACEMENT_CHARS['='] = "\\u003d"; 28 | HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027"; 29 | } 30 | 31 | public static String jsonEscape(String value) { 32 | final StringWriter out = new StringWriter(); 33 | String[] replacements = HTML_SAFE_REPLACEMENT_CHARS; 34 | int last = 0; 35 | int length = value.length(); 36 | for (int i = 0; i < length; i++) { 37 | char c = value.charAt(i); 38 | String replacement; 39 | if (c < 128) { 40 | replacement = replacements[c]; 41 | if (replacement == null) { 42 | continue; 43 | } 44 | } else if (c == '\u2028') { 45 | replacement = "\\u2028"; 46 | } else if (c == '\u2029') { 47 | replacement = "\\u2029"; 48 | } else { 49 | continue; 50 | } 51 | if (last < i) { 52 | out.write(value, last, i - last); 53 | } 54 | out.write(replacement); 55 | last = i + 1; 56 | } 57 | if (last < length) { 58 | out.write(value, last, length - last); 59 | } 60 | return out.toString(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/service/PrnfbRenderer.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.service; 2 | 3 | import static com.google.common.base.Charsets.UTF_8; 4 | import static com.google.common.base.Strings.isNullOrEmpty; 5 | import static com.google.common.base.Throwables.propagate; 6 | import static java.net.URLEncoder.encode; 7 | import static org.slf4j.LoggerFactory.getLogger; 8 | import static se.bjurr.prnfb.service.JsonEscaper.jsonEscape; 9 | import static se.bjurr.prnfb.service.PrnfbRenderer.ENCODE_FOR.HTML; 10 | import static se.bjurr.prnfb.service.PrnfbRenderer.ENCODE_FOR.JSON; 11 | import static se.bjurr.prnfb.service.PrnfbRenderer.ENCODE_FOR.URL; 12 | import static se.bjurr.prnfb.service.PrnfbVariable.EVERYTHING_URL; 13 | 14 | import com.atlassian.bitbucket.pull.PullRequest; 15 | import com.atlassian.bitbucket.repository.RepositoryService; 16 | import com.atlassian.bitbucket.server.ApplicationPropertiesService; 17 | import com.atlassian.bitbucket.user.ApplicationUser; 18 | import com.atlassian.bitbucket.user.SecurityService; 19 | import com.google.common.annotations.VisibleForTesting; 20 | import com.google.common.base.Supplier; 21 | import java.io.UnsupportedEncodingException; 22 | import java.util.Map; 23 | import java.util.regex.Matcher; 24 | import org.apache.commons.lang3.StringEscapeUtils; 25 | import org.slf4j.Logger; 26 | import se.bjurr.prnfb.http.ClientKeyStore; 27 | import se.bjurr.prnfb.listener.PrnfbPullRequestAction; 28 | import se.bjurr.prnfb.settings.PrnfbNotification; 29 | 30 | public class PrnfbRenderer { 31 | public enum ENCODE_FOR { 32 | NONE, 33 | URL, 34 | HTML, 35 | JSON 36 | } 37 | 38 | private static final Logger LOG = getLogger(PrnfbRenderer.class); 39 | private final ApplicationUser applicationUser; 40 | private final PrnfbNotification prnfbNotification; 41 | private final ApplicationPropertiesService propertiesService; 42 | private final PullRequest pullRequest; 43 | private final PrnfbPullRequestAction pullRequestAction; 44 | private final RepositoryService repositoryService; 45 | private final SecurityService securityService; 46 | 47 | /** 48 | * Contains special variables that are only available for specific events like {@link 49 | * PrnfbVariable#BUTTON_TRIGGER_TITLE} and {@link PrnfbVariable#PULL_REQUEST_COMMENT_TEXT}. 50 | */ 51 | private final Map> variables; 52 | 53 | PrnfbRenderer( 54 | PullRequest pullRequest, 55 | PrnfbPullRequestAction pullRequestAction, 56 | ApplicationUser applicationUser, 57 | RepositoryService repositoryService, 58 | ApplicationPropertiesService propertiesService, 59 | PrnfbNotification prnfbNotification, 60 | Map> variables, 61 | SecurityService securityService) { 62 | this.pullRequest = pullRequest; 63 | this.pullRequestAction = pullRequestAction; 64 | this.applicationUser = applicationUser; 65 | this.repositoryService = repositoryService; 66 | this.prnfbNotification = prnfbNotification; 67 | this.propertiesService = propertiesService; 68 | this.variables = variables; 69 | this.securityService = securityService; 70 | } 71 | 72 | private boolean containsVariable(String string, final String regExpStr) { 73 | if (isNullOrEmpty(string)) { 74 | return false; 75 | } 76 | return string.contains(regExpStr.replaceAll("\\\\", "")); 77 | } 78 | 79 | @VisibleForTesting 80 | String getRenderedStringResolved( 81 | String string, ENCODE_FOR encodeFor, final String regExpStr, String resolved) { 82 | String replaceWith = null; 83 | if (encodeFor == URL) { 84 | try { 85 | replaceWith = encode(resolved, UTF_8.name()); 86 | } catch (final UnsupportedEncodingException e) { 87 | propagate(e); 88 | } 89 | } else if (encodeFor == HTML) { 90 | replaceWith = StringEscapeUtils.escapeHtml4(resolved).replaceAll("(\r\n|\n)", "
"); 91 | } else if (encodeFor == JSON) { 92 | replaceWith = jsonEscape(resolved); 93 | } else { 94 | replaceWith = resolved; 95 | } 96 | try { 97 | replaceWith = Matcher.quoteReplacement(replaceWith); 98 | string = string.replaceAll(regExpStr, replaceWith); 99 | } catch (final IllegalArgumentException e) { 100 | throw new RuntimeException("Tried to replace " + regExpStr + " with " + replaceWith, e); 101 | } 102 | return string; 103 | } 104 | 105 | @VisibleForTesting 106 | String regexp(PrnfbVariable variable) { 107 | return "\\$\\{" + variable.name() + "\\}"; 108 | } 109 | 110 | public String render( 111 | String string, 112 | ENCODE_FOR encodeFor, 113 | ClientKeyStore clientKeyStore, 114 | Boolean shouldAcceptAnyCertificate) { 115 | string = 116 | renderVariable( 117 | string, ENCODE_FOR.NONE, clientKeyStore, shouldAcceptAnyCertificate, EVERYTHING_URL); 118 | 119 | for (final PrnfbVariable variable : PrnfbVariable.values()) { 120 | string = 121 | renderVariable(string, encodeFor, clientKeyStore, shouldAcceptAnyCertificate, variable); 122 | } 123 | return string; 124 | } 125 | 126 | private String renderVariable( 127 | String string, 128 | ENCODE_FOR encodeFor, 129 | ClientKeyStore clientKeyStore, 130 | Boolean shouldAcceptAnyCertificate, 131 | final PrnfbVariable variable) { 132 | final String regExpStr = regexp(variable); 133 | if (containsVariable(string, regExpStr)) { 134 | String resolved = ""; 135 | try { 136 | resolved = 137 | variable.resolve( 138 | pullRequest, 139 | pullRequestAction, 140 | applicationUser, 141 | repositoryService, 142 | propertiesService, 143 | prnfbNotification, 144 | variables, 145 | clientKeyStore, 146 | shouldAcceptAnyCertificate, 147 | securityService); 148 | if (resolved == null) { 149 | resolved = ""; 150 | } 151 | } catch (final Exception e) { 152 | LOG.error("Error when resolving " + variable, e); 153 | } 154 | return getRenderedStringResolved(string, encodeFor, regExpStr, resolved); 155 | } 156 | return string; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/service/PrnfbRendererFactory.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.service; 2 | 3 | import com.atlassian.bitbucket.auth.AuthenticationContext; 4 | import com.atlassian.bitbucket.pull.PullRequest; 5 | import com.atlassian.bitbucket.repository.RepositoryService; 6 | import com.atlassian.bitbucket.server.ApplicationPropertiesService; 7 | import com.atlassian.bitbucket.user.ApplicationUser; 8 | import com.atlassian.bitbucket.user.SecurityService; 9 | import se.bjurr.prnfb.http.ClientKeyStore; 10 | import se.bjurr.prnfb.listener.PrnfbPullRequestAction; 11 | import se.bjurr.prnfb.settings.PrnfbNotification; 12 | 13 | public class PrnfbRendererFactory { 14 | 15 | private final AuthenticationContext authenticationContext; 16 | private final ApplicationPropertiesService propertiesService; 17 | private final RepositoryService repositoryService; 18 | private final SecurityService securityService; 19 | 20 | public PrnfbRendererFactory( 21 | RepositoryService repositoryService, 22 | ApplicationPropertiesService propertiesService, 23 | AuthenticationContext authenticationContext, 24 | SecurityService securityService) { 25 | this.repositoryService = repositoryService; 26 | this.propertiesService = propertiesService; 27 | this.authenticationContext = authenticationContext; 28 | this.securityService = securityService; 29 | } 30 | 31 | public PrnfbRendererWrapper create( 32 | PullRequest pullRequest, 33 | PrnfbPullRequestAction pullRequestAction, 34 | VariablesContext variables, 35 | ClientKeyStore clientKeyStore, 36 | boolean shouldAcceptAnyCertificate) { 37 | PrnfbNotification prnfbNotification = null; 38 | PrnfbRenderer renderer = create(pullRequest, pullRequestAction, prnfbNotification, variables); 39 | return new PrnfbRendererWrapper(renderer, clientKeyStore, shouldAcceptAnyCertificate); 40 | } 41 | 42 | public PrnfbRenderer create( 43 | PullRequest pullRequest, 44 | PrnfbPullRequestAction pullRequestAction, 45 | PrnfbNotification prnfbNotification, 46 | VariablesContext variables) { 47 | return create( 48 | pullRequest, 49 | pullRequestAction, 50 | prnfbNotification, 51 | variables, 52 | this.authenticationContext.getCurrentUser()); 53 | } 54 | 55 | public PrnfbRenderer create( 56 | PullRequest pullRequest, 57 | PrnfbPullRequestAction pullRequestAction, 58 | PrnfbNotification prnfbNotification, 59 | VariablesContext variables, 60 | ApplicationUser currentUser) { 61 | return new PrnfbRenderer( 62 | pullRequest, 63 | pullRequestAction, 64 | currentUser, 65 | this.repositoryService, 66 | this.propertiesService, 67 | prnfbNotification, 68 | variables.getVariables(), 69 | this.securityService); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/service/PrnfbRendererWrapper.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.service; 2 | 3 | import se.bjurr.prnfb.http.ClientKeyStore; 4 | import se.bjurr.prnfb.service.PrnfbRenderer.ENCODE_FOR; 5 | 6 | /** 7 | * It may be expensive to create a new {@link PrnfbRenderer} in the service for every string that 8 | * should be rendered. If the service instead returns an instance of this class, then it will help 9 | * performance. 10 | */ 11 | public class PrnfbRendererWrapper { 12 | 13 | private final ClientKeyStore clientKeyStore; 14 | private final PrnfbRenderer renderer; 15 | private final Boolean shouldAcceptAnyCertificate; 16 | 17 | public PrnfbRendererWrapper( 18 | PrnfbRenderer renderer, ClientKeyStore clientKeyStore, Boolean shouldAcceptAnyCertificate) { 19 | this.renderer = renderer; 20 | this.clientKeyStore = clientKeyStore; 21 | this.shouldAcceptAnyCertificate = shouldAcceptAnyCertificate; 22 | } 23 | 24 | public String render(String inputString, ENCODE_FOR encodeFor) { 25 | return renderer.render(inputString, encodeFor, clientKeyStore, shouldAcceptAnyCertificate); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/service/PrnfbVariableResolver.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.service; 2 | 3 | import com.atlassian.bitbucket.pull.PullRequest; 4 | import com.atlassian.bitbucket.repository.RepositoryService; 5 | import com.atlassian.bitbucket.server.ApplicationPropertiesService; 6 | import com.atlassian.bitbucket.user.ApplicationUser; 7 | import com.atlassian.bitbucket.user.SecurityService; 8 | import com.google.common.base.Supplier; 9 | import java.util.Map; 10 | import se.bjurr.prnfb.http.ClientKeyStore; 11 | import se.bjurr.prnfb.listener.PrnfbPullRequestAction; 12 | import se.bjurr.prnfb.settings.PrnfbNotification; 13 | 14 | public interface PrnfbVariableResolver { 15 | 16 | String resolve( 17 | PullRequest pullRequest, 18 | PrnfbPullRequestAction pullRequestAction, 19 | ApplicationUser applicationUser, 20 | RepositoryService repositoryService, 21 | ApplicationPropertiesService propertiesService, 22 | PrnfbNotification prnfbNotification, 23 | Map> variables, 24 | ClientKeyStore clientKeyStore, 25 | boolean shouldAcceptAnyCertificate, 26 | SecurityService securityService); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/service/RepoProtocol.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.service; 2 | 3 | public enum RepoProtocol { 4 | http, 5 | ssh 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/service/UserCheckService.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.service; 2 | 3 | import static com.atlassian.bitbucket.permission.Permission.PROJECT_ADMIN; 4 | import static com.atlassian.bitbucket.permission.Permission.REPO_ADMIN; 5 | import static com.atlassian.bitbucket.permission.Permission.SYS_ADMIN; 6 | import static com.google.common.base.Strings.emptyToNull; 7 | import static com.google.common.collect.Iterables.filter; 8 | import static org.slf4j.LoggerFactory.getLogger; 9 | import static se.bjurr.prnfb.settings.USER_LEVEL.ADMIN; 10 | import static se.bjurr.prnfb.settings.USER_LEVEL.EVERYONE; 11 | 12 | import com.atlassian.bitbucket.permission.PermissionService; 13 | import com.atlassian.bitbucket.project.Project; 14 | import com.atlassian.bitbucket.project.ProjectService; 15 | import com.atlassian.bitbucket.repository.Repository; 16 | import com.atlassian.bitbucket.repository.RepositoryService; 17 | import com.atlassian.bitbucket.user.SecurityService; 18 | import com.atlassian.bitbucket.util.Operation; 19 | import com.atlassian.sal.api.user.UserKey; 20 | import com.atlassian.sal.api.user.UserManager; 21 | import com.atlassian.sal.api.user.UserProfile; 22 | import com.google.common.annotations.VisibleForTesting; 23 | import java.util.List; 24 | import javax.annotation.Nullable; 25 | import org.slf4j.Logger; 26 | import se.bjurr.prnfb.settings.Restricted; 27 | import se.bjurr.prnfb.settings.USER_LEVEL; 28 | 29 | public class UserCheckService { 30 | private static final Logger LOG = getLogger(UserCheckService.class); 31 | private final PermissionService permissionService; 32 | private final ProjectService projectService; 33 | private final RepositoryService repositoryService; 34 | private final SecurityService securityService; 35 | private final SettingsService settingsService; 36 | private final UserManager userManager; 37 | 38 | public UserCheckService( 39 | PermissionService permissionService, 40 | UserManager userManager, 41 | SettingsService settingsService, 42 | RepositoryService repositoryService, 43 | ProjectService projectService, 44 | SecurityService securityService) { 45 | this.userManager = userManager; 46 | this.settingsService = settingsService; 47 | this.permissionService = permissionService; 48 | this.projectService = projectService; 49 | this.repositoryService = repositoryService; 50 | this.securityService = securityService; 51 | } 52 | 53 | public Iterable filterAllowed(USER_LEVEL adminRestriction, List r) { 54 | return filter( 55 | r, 56 | (c) -> 57 | isAllowed( // 58 | adminRestriction, // 59 | c.getProjectKey().orNull(), // 60 | c.getRepositorySlug().orNull())); 61 | } 62 | 63 | public Iterable filterAdminAllowed(List list) { 64 | final USER_LEVEL adminRestriction = 65 | settingsService.getPrnfbSettingsData().getAdminRestriction(); 66 | return filter(list, (r) -> isAdminAllowed(r, adminRestriction)); 67 | } 68 | 69 | @VisibleForTesting 70 | private Project getProject(String projectKey) { 71 | try { 72 | return securityService // 73 | .withPermission(SYS_ADMIN, "Getting project") // 74 | .call( 75 | new Operation() { 76 | @Override 77 | public Project perform() throws Exception { 78 | return projectService.getByKey(projectKey); 79 | } 80 | }); 81 | } catch (final Exception e) { 82 | LOG.error(e.getMessage(), e); 83 | return null; 84 | } 85 | } 86 | 87 | @VisibleForTesting 88 | Repository getRepo(String projectKey, String repositorySlug) { 89 | try { 90 | return securityService // 91 | .withPermission(SYS_ADMIN, "Getting repo") // 92 | .call( 93 | new Operation() { 94 | @Override 95 | public Repository perform() throws Exception { 96 | return repositoryService.getBySlug(projectKey, repositorySlug); 97 | } 98 | }); 99 | } catch (final Exception e) { 100 | LOG.error(e.getMessage(), e); 101 | return null; 102 | } 103 | } 104 | 105 | public boolean isAdmin(UserKey userKey, String projectKey, String repositorySlug) { 106 | final boolean isAdmin = userManager.isAdmin(userKey); 107 | if (isAdmin) { 108 | return isAdmin; 109 | } 110 | 111 | projectKey = emptyToNull(projectKey); 112 | repositorySlug = emptyToNull(repositorySlug); 113 | 114 | if (projectKey != null && repositorySlug == null) { 115 | final Project project = getProject(projectKey); 116 | if (project == null) { 117 | LOG.error( 118 | "Project " 119 | + projectKey 120 | + " configured. But no such project exists! Allowing anyone to admin."); 121 | return true; 122 | } 123 | final boolean isAllowed = permissionService.hasProjectPermission(project, PROJECT_ADMIN); 124 | if (isAllowed) { 125 | return true; 126 | } 127 | } 128 | 129 | if (projectKey != null && repositorySlug != null) { 130 | final Repository repository = getRepo(projectKey, repositorySlug); 131 | if (repository == null) { 132 | LOG.error( 133 | "Project " 134 | + projectKey 135 | + " and repo " 136 | + repositorySlug 137 | + " configured. But no such repo exists! Allowing anyone to admin."); 138 | return true; 139 | } 140 | return permissionService.hasRepositoryPermission(repository, REPO_ADMIN); 141 | } 142 | return false; 143 | } 144 | 145 | public boolean isAdminAllowed(Restricted restricted, USER_LEVEL adminRestriction) { 146 | final String projectKey = restricted.getProjectKey().orNull(); 147 | final String repositorySlug = restricted.getRepositorySlug().orNull(); 148 | return isAllowed(adminRestriction, projectKey, repositorySlug); 149 | } 150 | 151 | public boolean isAllowed( 152 | USER_LEVEL userLevel, @Nullable String projectKey, @Nullable String repositorySlug) { 153 | final UserKey userKey = userManager.getRemoteUser().getUserKey(); 154 | final boolean isAdmin = isAdmin(userKey, projectKey, repositorySlug); 155 | final boolean isSystemAdmin = isSystemAdmin(userKey); 156 | return isAllowed(userLevel, isAdmin, isSystemAdmin); 157 | } 158 | 159 | boolean isAllowed(USER_LEVEL userLevel, boolean isAdmin, boolean isSystemAdmin) { 160 | return userLevel == EVERYONE // 161 | || isSystemAdmin // 162 | || isAdmin && userLevel == ADMIN; 163 | } 164 | 165 | public boolean isSystemAdmin(UserKey userKey) { 166 | return userManager.isSystemAdmin(userKey); 167 | } 168 | 169 | public boolean isViewAllowed() { 170 | final UserProfile user = userManager.getRemoteUser(); 171 | if (user == null) { 172 | return false; 173 | } 174 | return true; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/service/VariablesContext.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.service; 2 | 3 | import static com.google.common.base.Strings.isNullOrEmpty; 4 | import static se.bjurr.prnfb.service.PrnfbVariable.BUTTON_FORM_DATA; 5 | import static se.bjurr.prnfb.service.PrnfbVariable.BUTTON_TRIGGER_TITLE; 6 | import static se.bjurr.prnfb.service.PrnfbVariable.PULL_REQUEST_COMMENT_ACTION; 7 | import static se.bjurr.prnfb.service.PrnfbVariable.PULL_REQUEST_COMMENT_ID; 8 | import static se.bjurr.prnfb.service.PrnfbVariable.PULL_REQUEST_COMMENT_TEXT; 9 | import static se.bjurr.prnfb.service.PrnfbVariable.PULL_REQUEST_MERGE_COMMIT; 10 | import static se.bjurr.prnfb.service.PrnfbVariable.PULL_REQUEST_PREVIOUS_FROM_HASH; 11 | import static se.bjurr.prnfb.service.PrnfbVariable.PULL_REQUEST_PREVIOUS_TO_HASH; 12 | import static se.bjurr.prnfb.service.PrnfbVariable.PULL_REQUEST_USER_GROUPS; 13 | 14 | import com.atlassian.bitbucket.event.pull.PullRequestCommentEvent; 15 | import com.atlassian.bitbucket.event.pull.PullRequestEvent; 16 | import com.atlassian.bitbucket.event.pull.PullRequestMergedEvent; 17 | import com.atlassian.bitbucket.event.pull.PullRequestRescopedEvent; 18 | import com.google.common.base.Joiner; 19 | import com.google.common.base.Supplier; 20 | import com.google.common.base.Suppliers; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import se.bjurr.prnfb.settings.PrnfbButton; 25 | 26 | /** 27 | * {@link PrnfbVariable} is becoming a bit messy with a lot of parameters to resolve different 28 | * variables. This is intended to replace all those parameters. 29 | */ 30 | public class VariablesContext { 31 | 32 | public static class VariablesContextBuilder { 33 | public PrnfbButton button; 34 | public PullRequestEvent pullRequestEvent; 35 | public String formData; 36 | public List groups; 37 | 38 | public VariablesContextBuilder setButton(PrnfbButton button) { 39 | this.button = button; 40 | return this; 41 | } 42 | 43 | public VariablesContextBuilder setFormData(String formData) { 44 | this.formData = formData; 45 | return this; 46 | } 47 | 48 | public VariablesContextBuilder setGroups(List groups) { 49 | this.groups = groups; 50 | return this; 51 | } 52 | 53 | public VariablesContextBuilder setPullRequestEvent(PullRequestEvent pullRequestEvent) { 54 | this.pullRequestEvent = pullRequestEvent; 55 | return this; 56 | } 57 | 58 | public VariablesContextBuilder() {} 59 | 60 | public VariablesContext build() { 61 | return new VariablesContext(this); 62 | } 63 | } 64 | 65 | private final PrnfbButton button; 66 | private final PullRequestEvent pullRequestEvent; 67 | private final String formData; 68 | private final List groups; 69 | 70 | public VariablesContext(VariablesContextBuilder b) { 71 | this.button = b.button; 72 | this.pullRequestEvent = b.pullRequestEvent; 73 | this.formData = b.formData; 74 | this.groups = b.groups; 75 | } 76 | 77 | public List getGroups() { 78 | return groups; 79 | } 80 | 81 | public Map> getVariables() { 82 | final Map> variables = new HashMap<>(); 83 | 84 | if (groups != null) { 85 | variables.put(PULL_REQUEST_USER_GROUPS, Suppliers.ofInstance(Joiner.on(',').join(groups))); 86 | } 87 | 88 | if (button != null) { 89 | variables.put(BUTTON_TRIGGER_TITLE, Suppliers.ofInstance(button.getName())); 90 | } 91 | 92 | if (!isNullOrEmpty(formData)) { 93 | variables.put(BUTTON_FORM_DATA, Suppliers.ofInstance(formData)); 94 | } 95 | 96 | if (pullRequestEvent != null) { 97 | if (pullRequestEvent instanceof PullRequestCommentEvent) { 98 | final PullRequestCommentEvent pullRequestCommentEvent = 99 | (PullRequestCommentEvent) pullRequestEvent; 100 | variables.put( 101 | PULL_REQUEST_COMMENT_TEXT, 102 | () -> { 103 | return pullRequestCommentEvent.getComment().getText(); 104 | }); 105 | variables.put( 106 | PULL_REQUEST_COMMENT_ACTION, 107 | () -> { 108 | return pullRequestCommentEvent.getCommentAction().name(); 109 | }); 110 | variables.put( 111 | PULL_REQUEST_COMMENT_ID, 112 | () -> { 113 | return pullRequestCommentEvent.getComment().getId() + ""; 114 | }); 115 | } else if (pullRequestEvent instanceof PullRequestRescopedEvent) { 116 | final PullRequestRescopedEvent pullRequestRescopedEvent = 117 | (PullRequestRescopedEvent) pullRequestEvent; 118 | variables.put( 119 | PULL_REQUEST_PREVIOUS_FROM_HASH, 120 | () -> { 121 | if (pullRequestEvent instanceof PullRequestRescopedEvent) { 122 | return pullRequestRescopedEvent.getPreviousFromHash(); 123 | } 124 | return ""; 125 | }); 126 | variables.put( 127 | PULL_REQUEST_PREVIOUS_TO_HASH, 128 | () -> { 129 | if (pullRequestEvent instanceof PullRequestRescopedEvent) { 130 | return pullRequestRescopedEvent.getPreviousToHash(); 131 | } 132 | return ""; 133 | }); 134 | } else if (pullRequestEvent instanceof PullRequestMergedEvent) { 135 | variables.put( 136 | PULL_REQUEST_MERGE_COMMIT, 137 | new Supplier() { 138 | @Override 139 | public String get() { 140 | return ((PullRequestMergedEvent) pullRequestEvent).getCommit().getId(); 141 | } 142 | }); 143 | } 144 | } 145 | 146 | return variables; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/HasUuid.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | import java.util.UUID; 4 | 5 | public interface HasUuid { 6 | 7 | UUID getUuid(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/PrnfbButton.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | import static com.google.common.base.MoreObjects.firstNonNull; 4 | import static com.google.common.base.Optional.fromNullable; 5 | import static com.google.common.base.Strings.emptyToNull; 6 | import static java.util.UUID.randomUUID; 7 | 8 | import com.google.common.base.Optional; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.UUID; 12 | import se.bjurr.prnfb.presentation.dto.ON_OR_OFF; 13 | 14 | public class PrnfbButton implements HasUuid, Restricted { 15 | 16 | private final ON_OR_OFF confirmation; 17 | private final String name; 18 | private final String projectKey; 19 | private final String repositorySlug; 20 | private final List buttonFormElementList; 21 | private final USER_LEVEL userLevel; 22 | private final UUID uuid; 23 | private final String confirmationText; 24 | private final String redirectUrl; 25 | 26 | public PrnfbButton( 27 | UUID uuid, 28 | String name, 29 | USER_LEVEL userLevel, 30 | ON_OR_OFF confirmation, 31 | String projectKey, 32 | String repositorySlug, 33 | String confirmationText, 34 | String redirectUrl, 35 | List buttonFormElementList) { 36 | this.uuid = firstNonNull(uuid, randomUUID()); 37 | this.name = name; 38 | this.userLevel = userLevel; 39 | this.confirmation = confirmation; 40 | this.repositorySlug = emptyToNull(repositorySlug); 41 | this.projectKey = emptyToNull(projectKey); 42 | this.confirmationText = emptyToNull(confirmationText); 43 | this.redirectUrl = emptyToNull(redirectUrl); 44 | this.buttonFormElementList = 45 | firstNonNull(buttonFormElementList, new ArrayList()); 46 | } 47 | 48 | public String getConfirmationText() { 49 | return confirmationText; 50 | } 51 | 52 | public ON_OR_OFF getConfirmation() { 53 | return this.confirmation; 54 | } 55 | 56 | public String getName() { 57 | return this.name; 58 | } 59 | 60 | public List getButtonFormElementList() { 61 | return buttonFormElementList; 62 | } 63 | 64 | @Override 65 | public Optional getProjectKey() { 66 | return fromNullable(this.projectKey); 67 | } 68 | 69 | @Override 70 | public Optional getRepositorySlug() { 71 | return fromNullable(this.repositorySlug); 72 | } 73 | 74 | public USER_LEVEL getUserLevel() { 75 | return this.userLevel; 76 | } 77 | 78 | @Override 79 | public UUID getUuid() { 80 | return this.uuid; 81 | } 82 | 83 | public String getRedirectUrl() { 84 | return redirectUrl; 85 | } 86 | 87 | @Override 88 | public boolean equals(Object obj) { 89 | if (this == obj) { 90 | return true; 91 | } 92 | if (obj == null) { 93 | return false; 94 | } 95 | if (getClass() != obj.getClass()) { 96 | return false; 97 | } 98 | PrnfbButton other = (PrnfbButton) obj; 99 | if (buttonFormElementList == null) { 100 | if (other.buttonFormElementList != null) { 101 | return false; 102 | } 103 | } else if (!buttonFormElementList.equals(other.buttonFormElementList)) { 104 | return false; 105 | } 106 | if (confirmation != other.confirmation) { 107 | return false; 108 | } 109 | if (confirmationText == null) { 110 | if (other.confirmationText != null) { 111 | return false; 112 | } 113 | } else if (!confirmationText.equals(other.confirmationText)) { 114 | return false; 115 | } 116 | if (name == null) { 117 | if (other.name != null) { 118 | return false; 119 | } 120 | } else if (!name.equals(other.name)) { 121 | return false; 122 | } 123 | if (projectKey == null) { 124 | if (other.projectKey != null) { 125 | return false; 126 | } 127 | } else if (!projectKey.equals(other.projectKey)) { 128 | return false; 129 | } 130 | if (repositorySlug == null) { 131 | if (other.repositorySlug != null) { 132 | return false; 133 | } 134 | } else if (!repositorySlug.equals(other.repositorySlug)) { 135 | return false; 136 | } 137 | if (userLevel != other.userLevel) { 138 | return false; 139 | } 140 | if (uuid == null) { 141 | if (other.uuid != null) { 142 | return false; 143 | } 144 | } else if (!uuid.equals(other.uuid)) { 145 | return false; 146 | } 147 | if (redirectUrl == null) { 148 | if (other.redirectUrl != null) { 149 | return false; 150 | } 151 | } else if (!redirectUrl.equals(other.redirectUrl)) { 152 | return false; 153 | } 154 | return true; 155 | } 156 | 157 | @Override 158 | public int hashCode() { 159 | final int prime = 31; 160 | int result = 1; 161 | result = 162 | prime * result + (buttonFormElementList == null ? 0 : buttonFormElementList.hashCode()); 163 | result = prime * result + (confirmation == null ? 0 : confirmation.hashCode()); 164 | result = prime * result + (confirmationText == null ? 0 : confirmationText.hashCode()); 165 | result = prime * result + (name == null ? 0 : name.hashCode()); 166 | result = prime * result + (projectKey == null ? 0 : projectKey.hashCode()); 167 | result = prime * result + (repositorySlug == null ? 0 : repositorySlug.hashCode()); 168 | result = prime * result + (userLevel == null ? 0 : userLevel.hashCode()); 169 | result = prime * result + (uuid == null ? 0 : uuid.hashCode()); 170 | result = prime * result + (redirectUrl == null ? 0 : redirectUrl.hashCode()); 171 | return result; 172 | } 173 | 174 | @Override 175 | public String toString() { 176 | return "PrnfbButton [confirmation=" 177 | + confirmation 178 | + ", name=" 179 | + name 180 | + ", projectKey=" 181 | + projectKey 182 | + ", repositorySlug=" 183 | + repositorySlug 184 | + ", buttonFormElementList=" 185 | + buttonFormElementList 186 | + ", userLevel=" 187 | + userLevel 188 | + ", uuid=" 189 | + uuid 190 | + ", confirmationText=" 191 | + confirmationText 192 | + ", redirectUrl=" 193 | + redirectUrl 194 | + "]"; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/PrnfbButtonFormElement.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | import static com.google.common.base.MoreObjects.firstNonNull; 4 | import static com.google.common.base.Preconditions.checkNotNull; 5 | import static com.google.common.base.Strings.emptyToNull; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import se.bjurr.prnfb.presentation.dto.ButtonFormType; 10 | 11 | public class PrnfbButtonFormElement { 12 | private final String defaultValue; 13 | private final String description; 14 | private final String label; 15 | private final String name; 16 | private final List buttonFormElementOptionList; 17 | private final boolean required; 18 | private final ButtonFormType type; 19 | 20 | public PrnfbButtonFormElement( 21 | String defaultValue, 22 | String description, 23 | String label, 24 | String name, 25 | List options, 26 | boolean required, 27 | ButtonFormType type) { 28 | this.defaultValue = emptyToNull(defaultValue); 29 | this.description = emptyToNull(description); 30 | this.label = checkNotNull(label, "label"); 31 | this.name = checkNotNull(name, "name"); 32 | this.buttonFormElementOptionList = 33 | firstNonNull(options, new ArrayList()); 34 | this.required = checkNotNull(required, required); 35 | this.type = checkNotNull(type, type); 36 | } 37 | 38 | @Override 39 | public boolean equals(Object obj) { 40 | if (this == obj) { 41 | return true; 42 | } 43 | if (obj == null) { 44 | return false; 45 | } 46 | if (getClass() != obj.getClass()) { 47 | return false; 48 | } 49 | PrnfbButtonFormElement other = (PrnfbButtonFormElement) obj; 50 | if (defaultValue == null) { 51 | if (other.defaultValue != null) { 52 | return false; 53 | } 54 | } else if (!defaultValue.equals(other.defaultValue)) { 55 | return false; 56 | } 57 | if (description == null) { 58 | if (other.description != null) { 59 | return false; 60 | } 61 | } else if (!description.equals(other.description)) { 62 | return false; 63 | } 64 | if (label == null) { 65 | if (other.label != null) { 66 | return false; 67 | } 68 | } else if (!label.equals(other.label)) { 69 | return false; 70 | } 71 | if (name == null) { 72 | if (other.name != null) { 73 | return false; 74 | } 75 | } else if (!name.equals(other.name)) { 76 | return false; 77 | } 78 | if (buttonFormElementOptionList == null) { 79 | if (other.buttonFormElementOptionList != null) { 80 | return false; 81 | } 82 | } else if (!buttonFormElementOptionList.equals(other.buttonFormElementOptionList)) { 83 | return false; 84 | } 85 | if (required != other.required) { 86 | return false; 87 | } 88 | if (type != other.type) { 89 | return false; 90 | } 91 | return true; 92 | } 93 | 94 | public String getDefaultValue() { 95 | return defaultValue; 96 | } 97 | 98 | public String getDescription() { 99 | return description; 100 | } 101 | 102 | public String getLabel() { 103 | return label; 104 | } 105 | 106 | public String getName() { 107 | return name; 108 | } 109 | 110 | public List getOptions() { 111 | return buttonFormElementOptionList; 112 | } 113 | 114 | public boolean getRequired() { 115 | return required; 116 | } 117 | 118 | public ButtonFormType getType() { 119 | return type; 120 | } 121 | 122 | @Override 123 | public int hashCode() { 124 | final int prime = 31; 125 | int result = 1; 126 | result = prime * result + (defaultValue == null ? 0 : defaultValue.hashCode()); 127 | result = prime * result + (description == null ? 0 : description.hashCode()); 128 | result = prime * result + (label == null ? 0 : label.hashCode()); 129 | result = prime * result + (name == null ? 0 : name.hashCode()); 130 | result = 131 | prime * result 132 | + (buttonFormElementOptionList == null ? 0 : buttonFormElementOptionList.hashCode()); 133 | result = prime * result + (required ? 1231 : 1237); 134 | result = prime * result + (type == null ? 0 : type.hashCode()); 135 | return result; 136 | } 137 | 138 | @Override 139 | public String toString() { 140 | return "ButtonFormDTO [options=" 141 | + buttonFormElementOptionList 142 | + ", name=" 143 | + name 144 | + ", label=" 145 | + label 146 | + ", defaultValue=" 147 | + defaultValue 148 | + ", type=" 149 | + type 150 | + ", required=" 151 | + required 152 | + ", description=" 153 | + description 154 | + "]"; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/PrnfbButtonFormElementOption.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | import static com.google.common.base.Preconditions.checkNotNull; 4 | 5 | public class PrnfbButtonFormElementOption { 6 | private final String label; 7 | private final String name; 8 | private final Boolean defaultValue; 9 | 10 | public PrnfbButtonFormElementOption(String label, String name, Boolean defaultValue) { 11 | this.label = checkNotNull(label, "label"); 12 | this.name = checkNotNull(name, "name"); 13 | this.defaultValue = defaultValue; 14 | } 15 | 16 | public Boolean getDefaultValue() { 17 | return defaultValue; 18 | } 19 | 20 | public String getLabel() { 21 | return label; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return "ButtonFormOptionDTO [label=" 31 | + label 32 | + ", name=" 33 | + name 34 | + ", defaultValue=" 35 | + defaultValue 36 | + "]"; 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | final int prime = 31; 42 | int result = 1; 43 | result = prime * result + (defaultValue == null ? 0 : defaultValue.hashCode()); 44 | result = prime * result + (label == null ? 0 : label.hashCode()); 45 | result = prime * result + (name == null ? 0 : name.hashCode()); 46 | return result; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object obj) { 51 | if (this == obj) { 52 | return true; 53 | } 54 | if (obj == null) { 55 | return false; 56 | } 57 | if (getClass() != obj.getClass()) { 58 | return false; 59 | } 60 | PrnfbButtonFormElementOption other = (PrnfbButtonFormElementOption) obj; 61 | if (defaultValue == null) { 62 | if (other.defaultValue != null) { 63 | return false; 64 | } 65 | } else if (!defaultValue.equals(other.defaultValue)) { 66 | return false; 67 | } 68 | if (label == null) { 69 | if (other.label != null) { 70 | return false; 71 | } 72 | } else if (!label.equals(other.label)) { 73 | return false; 74 | } 75 | if (name == null) { 76 | if (other.name != null) { 77 | return false; 78 | } 79 | } else if (!name.equals(other.name)) { 80 | return false; 81 | } 82 | return true; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/PrnfbHeader.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | import static com.google.common.base.Preconditions.checkNotNull; 4 | import static com.google.common.base.Strings.emptyToNull; 5 | import static com.google.common.base.Strings.nullToEmpty; 6 | 7 | public class PrnfbHeader { 8 | 9 | private final String name; 10 | private final String value; 11 | 12 | public PrnfbHeader(String name, String value) { 13 | this.name = checkNotNull(emptyToNull(nullToEmpty(name).trim()), "Header name must be set"); 14 | this.value = checkNotNull(emptyToNull(nullToEmpty(value).trim()), "Header value must be set"); 15 | } 16 | 17 | @Override 18 | public boolean equals(Object obj) { 19 | if (this == obj) { 20 | return true; 21 | } 22 | if (obj == null) { 23 | return false; 24 | } 25 | if (getClass() != obj.getClass()) { 26 | return false; 27 | } 28 | PrnfbHeader other = (PrnfbHeader) obj; 29 | if (this.name == null) { 30 | if (other.name != null) { 31 | return false; 32 | } 33 | } else if (!this.name.equals(other.name)) { 34 | return false; 35 | } 36 | if (this.value == null) { 37 | if (other.value != null) { 38 | return false; 39 | } 40 | } else if (!this.value.equals(other.value)) { 41 | return false; 42 | } 43 | return true; 44 | } 45 | 46 | public String getName() { 47 | return this.name; 48 | } 49 | 50 | public String getValue() { 51 | return this.value; 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | final int prime = 31; 57 | int result = 1; 58 | result = prime * result + (this.name == null ? 0 : this.name.hashCode()); 59 | result = prime * result + (this.value == null ? 0 : this.value.hashCode()); 60 | return result; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return "PrnfbHeader [name=" + this.name + ", value=" + this.value + "]"; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/PrnfbSettings.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | import static com.google.common.base.Preconditions.checkNotNull; 4 | import static com.google.common.collect.Lists.newArrayList; 5 | 6 | import java.util.List; 7 | 8 | public class PrnfbSettings { 9 | public static final String UNCHANGED = "KEEP_THIS_TO_LEAVE_UNCHANGED"; 10 | private final List buttons; 11 | private List notifications = newArrayList(); 12 | private final PrnfbSettingsData prnfbSettingsData; 13 | 14 | public PrnfbSettings(PrnfbSettingsBuilder builder) { 15 | this.notifications = checkNotNull(builder.getNotifications()); 16 | this.buttons = checkNotNull(builder.getButtons()); 17 | this.prnfbSettingsData = checkNotNull(builder.getPrnfbSettingsData(), "prnfbSettingsData"); 18 | } 19 | 20 | @Override 21 | public boolean equals(Object obj) { 22 | if (this == obj) { 23 | return true; 24 | } 25 | if (obj == null) { 26 | return false; 27 | } 28 | if (getClass() != obj.getClass()) { 29 | return false; 30 | } 31 | PrnfbSettings other = (PrnfbSettings) obj; 32 | if (this.buttons == null) { 33 | if (other.buttons != null) { 34 | return false; 35 | } 36 | } else if (!this.buttons.equals(other.buttons)) { 37 | return false; 38 | } 39 | if (this.notifications == null) { 40 | if (other.notifications != null) { 41 | return false; 42 | } 43 | } else if (!this.notifications.equals(other.notifications)) { 44 | return false; 45 | } 46 | if (this.prnfbSettingsData == null) { 47 | if (other.prnfbSettingsData != null) { 48 | return false; 49 | } 50 | } else if (!this.prnfbSettingsData.equals(other.prnfbSettingsData)) { 51 | return false; 52 | } 53 | return true; 54 | } 55 | 56 | public List getButtons() { 57 | return this.buttons; 58 | } 59 | 60 | public List getNotifications() { 61 | return this.notifications; 62 | } 63 | 64 | public PrnfbSettingsData getPrnfbSettingsData() { 65 | return this.prnfbSettingsData; 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | final int prime = 31; 71 | int result = 1; 72 | result = prime * result + (this.buttons == null ? 0 : this.buttons.hashCode()); 73 | result = prime * result + (this.notifications == null ? 0 : this.notifications.hashCode()); 74 | result = 75 | prime * result + (this.prnfbSettingsData == null ? 0 : this.prnfbSettingsData.hashCode()); 76 | return result; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/PrnfbSettingsBuilder.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | import static com.google.common.base.MoreObjects.firstNonNull; 4 | import static com.google.common.collect.Lists.newArrayList; 5 | import static se.bjurr.prnfb.settings.PrnfbSettingsDataBuilder.prnfbSettingsDataBuilder; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class PrnfbSettingsBuilder { 11 | public static PrnfbSettingsBuilder prnfbSettingsBuilder() { 12 | return new PrnfbSettingsBuilder(); 13 | } 14 | 15 | public static PrnfbSettingsBuilder prnfbSettingsBuilder(PrnfbSettings settings) { 16 | return new PrnfbSettingsBuilder(settings); 17 | } 18 | 19 | private List buttons; 20 | private List notifications; 21 | private PrnfbSettingsData prnfbSettingsData; 22 | 23 | private PrnfbSettingsBuilder() { 24 | this.notifications = newArrayList(); 25 | this.buttons = newArrayList(); 26 | this.prnfbSettingsData = 27 | prnfbSettingsDataBuilder() // 28 | .build(); 29 | } 30 | 31 | private PrnfbSettingsBuilder(PrnfbSettings settings) { 32 | this.notifications = 33 | firstNonNull(settings.getNotifications(), new ArrayList()); 34 | this.buttons = firstNonNull(settings.getButtons(), new ArrayList()); 35 | this.prnfbSettingsData = settings.getPrnfbSettingsData(); 36 | } 37 | 38 | public PrnfbSettings build() { 39 | return new PrnfbSettings(this); 40 | } 41 | 42 | public List getButtons() { 43 | return this.buttons; 44 | } 45 | 46 | public List getNotifications() { 47 | return this.notifications; 48 | } 49 | 50 | public PrnfbSettingsData getPrnfbSettingsData() { 51 | return this.prnfbSettingsData; 52 | } 53 | 54 | public PrnfbSettingsBuilder setButtons(List buttons) { 55 | this.buttons = buttons; 56 | return this; 57 | } 58 | 59 | public PrnfbSettingsBuilder setNotifications(List notifications) { 60 | this.notifications = notifications; 61 | return this; 62 | } 63 | 64 | public PrnfbSettingsBuilder setPrnfbSettingsData(PrnfbSettingsData prnfbSettingsData) { 65 | this.prnfbSettingsData = prnfbSettingsData; 66 | return this; 67 | } 68 | 69 | public PrnfbSettingsBuilder withButton(PrnfbButton prnfbButton) { 70 | this.buttons.add(prnfbButton); 71 | return this; 72 | } 73 | 74 | public PrnfbSettingsBuilder withNotification(PrnfbNotification notification) { 75 | this.notifications.add(notification); 76 | return this; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/PrnfbSettingsData.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | import static com.google.common.base.Optional.fromNullable; 4 | import static com.google.common.base.Strings.emptyToNull; 5 | 6 | import com.google.common.base.Optional; 7 | 8 | public class PrnfbSettingsData { 9 | private final USER_LEVEL adminRestriction; 10 | private final String keyStore; 11 | private final String keyStorePassword; 12 | private final String keyStoreType; 13 | private final boolean shouldAcceptAnyCertificate; 14 | 15 | public PrnfbSettingsData() { 16 | this.keyStore = null; 17 | this.keyStoreType = null; 18 | this.keyStorePassword = null; 19 | this.shouldAcceptAnyCertificate = false; 20 | this.adminRestriction = null; 21 | } 22 | 23 | public PrnfbSettingsData(PrnfbSettingsDataBuilder builder) { 24 | this.keyStore = emptyToNull(builder.getKeyStore()); 25 | this.keyStoreType = builder.getKeyStoreType(); 26 | this.keyStorePassword = emptyToNull(builder.getKeyStorePassword()); 27 | this.shouldAcceptAnyCertificate = builder.shouldAcceptAnyCertificate(); 28 | this.adminRestriction = builder.getAdminRestriction(); 29 | } 30 | 31 | @Override 32 | public boolean equals(Object obj) { 33 | if (this == obj) { 34 | return true; 35 | } 36 | if (obj == null) { 37 | return false; 38 | } 39 | if (getClass() != obj.getClass()) { 40 | return false; 41 | } 42 | PrnfbSettingsData other = (PrnfbSettingsData) obj; 43 | if (this.adminRestriction != other.adminRestriction) { 44 | return false; 45 | } 46 | if (this.keyStore == null) { 47 | if (other.keyStore != null) { 48 | return false; 49 | } 50 | } else if (!this.keyStore.equals(other.keyStore)) { 51 | return false; 52 | } 53 | if (this.keyStorePassword == null) { 54 | if (other.keyStorePassword != null) { 55 | return false; 56 | } 57 | } else if (!this.keyStorePassword.equals(other.keyStorePassword)) { 58 | return false; 59 | } 60 | if (this.keyStoreType == null) { 61 | if (other.keyStoreType != null) { 62 | return false; 63 | } 64 | } else if (!this.keyStoreType.equals(other.keyStoreType)) { 65 | return false; 66 | } 67 | if (this.shouldAcceptAnyCertificate != other.shouldAcceptAnyCertificate) { 68 | return false; 69 | } 70 | return true; 71 | } 72 | 73 | public USER_LEVEL getAdminRestriction() { 74 | return this.adminRestriction; 75 | } 76 | 77 | public Optional getKeyStore() { 78 | return fromNullable(this.keyStore); 79 | } 80 | 81 | public Optional getKeyStorePassword() { 82 | return fromNullable(this.keyStorePassword); 83 | } 84 | 85 | public String getKeyStoreType() { 86 | return this.keyStoreType; 87 | } 88 | 89 | @Override 90 | public int hashCode() { 91 | final int prime = 31; 92 | int result = 1; 93 | result = 94 | prime * result + ((this.adminRestriction == null) ? 0 : this.adminRestriction.hashCode()); 95 | result = prime * result + ((this.keyStore == null) ? 0 : this.keyStore.hashCode()); 96 | result = 97 | prime * result + ((this.keyStorePassword == null) ? 0 : this.keyStorePassword.hashCode()); 98 | result = prime * result + ((this.keyStoreType == null) ? 0 : this.keyStoreType.hashCode()); 99 | result = prime * result + (this.shouldAcceptAnyCertificate ? 1231 : 1237); 100 | return result; 101 | } 102 | 103 | public boolean isShouldAcceptAnyCertificate() { 104 | return this.shouldAcceptAnyCertificate; 105 | } 106 | 107 | @Override 108 | public String toString() { 109 | return "PrnfbSettingsData [keyStore=" 110 | + this.keyStore 111 | + ", keyStoreType=" 112 | + this.keyStoreType 113 | + ", keyStorePassword=" 114 | + this.keyStorePassword 115 | + ", shouldAcceptAnyCertificate=" 116 | + this.shouldAcceptAnyCertificate 117 | + ", adminRestriction=" 118 | + this.adminRestriction 119 | + "]"; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/PrnfbSettingsDataBuilder.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | public class PrnfbSettingsDataBuilder { 4 | public static PrnfbSettingsDataBuilder prnfbSettingsDataBuilder() { 5 | return new PrnfbSettingsDataBuilder(); 6 | } 7 | 8 | public static PrnfbSettingsDataBuilder prnfbSettingsDataBuilder(PrnfbSettingsData settings) { 9 | return new PrnfbSettingsDataBuilder(settings); 10 | } 11 | 12 | private USER_LEVEL adminRestriction; 13 | private String keyStore; 14 | private String keyStorePassword; 15 | private String keyStoreType; 16 | private boolean shouldAcceptAnyCertificate; 17 | 18 | private PrnfbSettingsDataBuilder() {} 19 | 20 | private PrnfbSettingsDataBuilder(PrnfbSettingsData settings) { 21 | this.shouldAcceptAnyCertificate = settings.isShouldAcceptAnyCertificate(); 22 | this.keyStore = settings.getKeyStore().orNull(); 23 | this.keyStoreType = settings.getKeyStoreType(); 24 | this.keyStorePassword = settings.getKeyStorePassword().orNull(); 25 | this.adminRestriction = settings.getAdminRestriction(); 26 | } 27 | 28 | public PrnfbSettingsData build() { 29 | return new PrnfbSettingsData(this); 30 | } 31 | 32 | public USER_LEVEL getAdminRestriction() { 33 | return this.adminRestriction; 34 | } 35 | 36 | public String getKeyStore() { 37 | return this.keyStore; 38 | } 39 | 40 | public String getKeyStorePassword() { 41 | return this.keyStorePassword; 42 | } 43 | 44 | public String getKeyStoreType() { 45 | return this.keyStoreType; 46 | } 47 | 48 | public PrnfbSettingsDataBuilder setAdminRestriction(USER_LEVEL adminRestriction) { 49 | this.adminRestriction = adminRestriction; 50 | return this; 51 | } 52 | 53 | public PrnfbSettingsDataBuilder setKeyStore(String keyStore) { 54 | this.keyStore = keyStore; 55 | return this; 56 | } 57 | 58 | public PrnfbSettingsDataBuilder setKeyStorePassword(String keyStorePassword) { 59 | this.keyStorePassword = keyStorePassword; 60 | return this; 61 | } 62 | 63 | public PrnfbSettingsDataBuilder setKeyStoreType(String keyStoreType) { 64 | this.keyStoreType = keyStoreType; 65 | return this; 66 | } 67 | 68 | public PrnfbSettingsDataBuilder setShouldAcceptAnyCertificate( 69 | boolean shouldAcceptAnyCertificate) { 70 | this.shouldAcceptAnyCertificate = shouldAcceptAnyCertificate; 71 | return this; 72 | } 73 | 74 | public boolean shouldAcceptAnyCertificate() { 75 | return this.shouldAcceptAnyCertificate; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/Restricted.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | import com.google.common.base.Optional; 4 | 5 | public interface Restricted { 6 | 7 | Optional getRepositorySlug(); 8 | 9 | Optional getProjectKey(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/TRIGGER_IF_MERGE.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | public enum TRIGGER_IF_MERGE { 4 | ALWAYS, 5 | CONFLICTING, 6 | NOT_CONFLICTING 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/USER_LEVEL.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | public enum USER_LEVEL { 4 | ADMIN, 5 | EVERYONE, 6 | SYSTEM_ADMIN 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/settings/ValidationException.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.settings; 2 | 3 | public class ValidationException extends Exception { 4 | private static final long serialVersionUID = 2203598567281456784L; 5 | private final String error; 6 | private final String field; 7 | 8 | public ValidationException() { 9 | this.error = null; 10 | this.field = null; 11 | } 12 | 13 | public ValidationException(String field, String error) { 14 | this.error = error; 15 | this.field = field; 16 | } 17 | 18 | public String getError() { 19 | return this.error; 20 | } 21 | 22 | public String getField() { 23 | return this.field; 24 | } 25 | 26 | @Override 27 | public String getMessage() { 28 | return this.field + "=" + this.error; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/transformer/ButtonTransformer.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.transformer; 2 | 3 | import static com.google.common.base.Strings.isNullOrEmpty; 4 | import static com.google.common.collect.Lists.newArrayList; 5 | import static se.bjurr.prnfb.presentation.dto.ButtonDTO.BUTTON_FORM_LIST_DTO_TYPE; 6 | import static se.bjurr.prnfb.presentation.dto.ButtonFormType.checkbox; 7 | import static se.bjurr.prnfb.presentation.dto.ButtonFormType.radio; 8 | 9 | import com.google.common.annotations.VisibleForTesting; 10 | import com.google.gson.Gson; 11 | import com.google.gson.GsonBuilder; 12 | import java.net.URI; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.UUID; 16 | import se.bjurr.prnfb.http.NotificationResponse; 17 | import se.bjurr.prnfb.presentation.dto.ButtonDTO; 18 | import se.bjurr.prnfb.presentation.dto.ButtonFormElementDTO; 19 | import se.bjurr.prnfb.presentation.dto.ButtonFormElementOptionDTO; 20 | import se.bjurr.prnfb.presentation.dto.ButtonPressDTO; 21 | import se.bjurr.prnfb.presentation.dto.NotificationResponseDTO; 22 | import se.bjurr.prnfb.settings.PrnfbButton; 23 | import se.bjurr.prnfb.settings.PrnfbButtonFormElement; 24 | import se.bjurr.prnfb.settings.PrnfbButtonFormElementOption; 25 | 26 | public class ButtonTransformer { 27 | private static final Gson gson = 28 | new GsonBuilder() // 29 | .setPrettyPrinting() // 30 | .create(); 31 | 32 | public static ButtonDTO toButtonDto(PrnfbButton from) { 33 | ButtonDTO to = new ButtonDTO(); 34 | to.setName(from.getName()); 35 | to.setUserLevel(from.getUserLevel()); 36 | to.setUuid(from.getUuid()); 37 | to.setRedirectUrl(from.getRedirectUrl()); 38 | to.setProjectKey(from.getProjectKey().orNull()); 39 | to.setRepositorySlug(from.getRepositorySlug().orNull()); 40 | to.setConfirmation(from.getConfirmation()); 41 | to.setConfirmationText(from.getConfirmationText()); 42 | to.setButtonFormList(toButtonFormDtoList(from.getButtonFormElementList())); 43 | String buttonFormDtoListString = gson.toJson(to.getButtonFormList()); 44 | to.setButtonFormListString(buttonFormDtoListString); 45 | return to; 46 | } 47 | 48 | private static List toButtonFormDtoList( 49 | List buttonFormElementList) { 50 | List to = new ArrayList<>(); 51 | if (buttonFormElementList != null) { 52 | for (PrnfbButtonFormElement from : buttonFormElementList) { 53 | to.add(toDto(from)); 54 | } 55 | } 56 | return to; 57 | } 58 | 59 | private static ButtonFormElementDTO toDto(PrnfbButtonFormElement from) { 60 | ButtonFormElementDTO to = new ButtonFormElementDTO(); 61 | to.setDefaultValue(from.getDefaultValue()); 62 | to.setDescription(from.getDescription()); 63 | to.setLabel(from.getLabel()); 64 | to.setName(from.getName()); 65 | to.setRequired(from.getRequired()); 66 | to.setType(from.getType()); 67 | to.setButtonFormElementOptionList(toDto(from.getOptions())); 68 | return to; 69 | } 70 | 71 | private static List toDto( 72 | List options) { 73 | List to = new ArrayList<>(); 74 | if (options != null) { 75 | for (PrnfbButtonFormElementOption from : options) { 76 | to.add(toDto(from)); 77 | } 78 | } 79 | return to; 80 | } 81 | 82 | private static ButtonFormElementOptionDTO toDto(PrnfbButtonFormElementOption from) { 83 | ButtonFormElementOptionDTO to = new ButtonFormElementOptionDTO(); 84 | to.setDefaultValue(from.getDefaultValue()); 85 | to.setLabel(from.getLabel()); 86 | to.setName(from.getName()); 87 | return to; 88 | } 89 | 90 | public static List toButtonDtoList(Iterable allowedButtons) { 91 | List to = newArrayList(); 92 | for (PrnfbButton from : allowedButtons) { 93 | to.add(toButtonDto(from)); 94 | } 95 | return to; 96 | } 97 | 98 | public static PrnfbButton toPrnfbButton(ButtonDTO buttonDto) { 99 | List buttonFormDto = buttonDto.getButtonFormList(); 100 | if (buttonFormDto == null 101 | || buttonFormDto.isEmpty() && !isNullOrEmpty(buttonDto.getButtonFormListString())) { 102 | buttonFormDto = gson.fromJson(buttonDto.getButtonFormListString(), BUTTON_FORM_LIST_DTO_TYPE); 103 | } 104 | validateButtonFormDTOList(buttonFormDto); 105 | List buttonFormElement = toPrnfbButtonElementList(buttonFormDto); 106 | return new PrnfbButton( // 107 | buttonDto.getUUID(), // 108 | buttonDto.getName(), // 109 | buttonDto.getUserLevel(), // 110 | buttonDto.getConfirmation(), // 111 | buttonDto.getProjectKey().orNull(), // 112 | buttonDto.getRepositorySlug().orNull(), // 113 | buttonDto.getConfirmationText(), // 114 | buttonDto.getRedirectUrl(), // 115 | buttonFormElement); // 116 | } 117 | 118 | @VisibleForTesting 119 | static void validateButtonFormDTOList(List buttonFormDtoList) throws Error { 120 | if (buttonFormDtoList == null || buttonFormDtoList.isEmpty()) { 121 | return; 122 | } 123 | for (ButtonFormElementDTO buttonFormDto : buttonFormDtoList) { 124 | if (isNullOrEmpty(buttonFormDto.getLabel())) { 125 | throw new Error("The label must be set."); 126 | } 127 | if (isNullOrEmpty(buttonFormDto.getName())) { 128 | throw new Error("The name must be set."); 129 | } 130 | if ((buttonFormDto.getType() == radio || buttonFormDto.getType() == checkbox) 131 | && (buttonFormDto.getButtonFormElementOptionList() == null 132 | || buttonFormDto.getButtonFormElementOptionList().isEmpty())) { 133 | throw new Error("When adding radio buttons, options must also be defined."); 134 | } 135 | } 136 | } 137 | 138 | private static List toPrnfbButtonElementList( 139 | List buttonFormDtoList) { 140 | List to = new ArrayList<>(); 141 | if (buttonFormDtoList != null) { 142 | for (ButtonFormElementDTO from : buttonFormDtoList) { 143 | to.add(to(from)); 144 | } 145 | } 146 | return to; 147 | } 148 | 149 | private static PrnfbButtonFormElement to(ButtonFormElementDTO from) { 150 | return new PrnfbButtonFormElement( 151 | from.getDefaultValue(), 152 | from.getDescription(), 153 | from.getLabel(), 154 | from.getName(), 155 | to(from.getButtonFormElementOptionList()), 156 | from.getRequired(), 157 | from.getType()); 158 | } 159 | 160 | private static List to(List options) { 161 | List to = new ArrayList<>(); 162 | if (options != null) { 163 | for (ButtonFormElementOptionDTO from : options) { 164 | to.add(to(from)); 165 | } 166 | } 167 | return to; 168 | } 169 | 170 | private static PrnfbButtonFormElementOption to(ButtonFormElementOptionDTO from) { 171 | PrnfbButtonFormElementOption to = 172 | new PrnfbButtonFormElementOption(from.getLabel(), from.getName(), from.getDefaultValue()); 173 | return to; 174 | } 175 | 176 | public static ButtonPressDTO toTriggerResultDto( 177 | PrnfbButton button, List results) { 178 | List notificationResponses = newArrayList(); 179 | for (NotificationResponse from : results) { 180 | String content = null; 181 | int status = 0; 182 | URI uri = null; 183 | if (from.getHttpResponse() != null) { 184 | content = from.getHttpResponse().getContent(); 185 | status = from.getHttpResponse().getStatus(); 186 | uri = from.getHttpResponse().getUri(); 187 | } 188 | UUID notification = from.getNotification(); 189 | String notificationName = from.getNotificationName(); 190 | notificationResponses.add( 191 | new NotificationResponseDTO(uri, content, status, notification, notificationName)); 192 | } 193 | return new ButtonPressDTO(button.getConfirmation(), notificationResponses); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/transformer/NotificationTransformer.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.transformer; 2 | 3 | import static com.google.common.base.Strings.isNullOrEmpty; 4 | import static com.google.common.collect.Lists.newArrayList; 5 | import static se.bjurr.prnfb.settings.PrnfbNotificationBuilder.prnfbNotificationBuilder; 6 | import static se.bjurr.prnfb.settings.PrnfbSettings.UNCHANGED; 7 | 8 | import com.atlassian.bitbucket.pull.PullRequestState; 9 | import java.util.List; 10 | import se.bjurr.prnfb.listener.PrnfbPullRequestAction; 11 | import se.bjurr.prnfb.presentation.dto.HeaderDTO; 12 | import se.bjurr.prnfb.presentation.dto.NotificationDTO; 13 | import se.bjurr.prnfb.settings.PrnfbHeader; 14 | import se.bjurr.prnfb.settings.PrnfbNotification; 15 | import se.bjurr.prnfb.settings.ValidationException; 16 | 17 | public class NotificationTransformer { 18 | 19 | public static NotificationDTO toNotificationDto(final PrnfbNotification from) { 20 | final NotificationDTO to = new NotificationDTO(); 21 | to.setProjectKey(from.getProjectKey().orNull()); 22 | to.setRepositorySlug(from.getRepositorySlug().orNull()); 23 | to.setFilterRegexp(from.getFilterRegexp().orNull()); 24 | to.setFilterString(from.getFilterString().orNull()); 25 | to.setInjectionUrl(from.getInjectionUrl().orNull()); 26 | to.setInjectionUrlRegexp(from.getInjectionUrlRegexp().orNull()); 27 | to.setVariableName(from.getVariableName().orNull()); 28 | to.setVariableRegex(from.getVariableRegex().orNull()); 29 | to.setMethod(from.getMethod()); 30 | to.setName(from.getName()); 31 | to.setHeaders(toHeaders(from.getHeaders())); 32 | to.setPostContent(from.getPostContent().orNull()); 33 | to.setPostContentEncoding(from.getPostContentEncoding()); 34 | to.setProxyPort(from.getProxyPort()); 35 | to.setProxyServer(from.getProxyServer().orNull()); 36 | to.setProxySchema(from.getProxySchema().orNull()); 37 | to.setProxyUser(UNCHANGED); 38 | to.setProxyPassword(UNCHANGED); 39 | to.setTriggerIfCanMerge(from.getTriggerIfCanMerge()); 40 | to.setTriggerIgnoreStateList(toPullRequestStateStrings(from.getTriggerIgnoreStateList())); 41 | to.setTriggers(toStrings(from.getTriggers())); 42 | to.setUpdatePullRequestRefs(from.isUpdatePullRequestRefs()); 43 | to.setUrl(from.getUrl()); 44 | to.setUser(UNCHANGED); 45 | to.setPassword(UNCHANGED); 46 | to.setUuid(from.getUuid()); 47 | to.setHttpVersion(from.getHttpVersion()); 48 | return to; 49 | } 50 | 51 | public static List toNotificationDtoList( 52 | final Iterable from) { 53 | final List to = newArrayList(); 54 | if (from != null) { 55 | for (final PrnfbNotification n : from) { 56 | to.add(toNotificationDto(n)); 57 | } 58 | } 59 | return to; 60 | } 61 | 62 | public static PrnfbNotification toPrnfbNotification(final NotificationDTO from) 63 | throws ValidationException { 64 | return prnfbNotificationBuilder() // 65 | .withFilterRegexp(from.getFilterRegexp()) // 66 | .withFilterString(from.getFilterString()) // 67 | .setHeaders(toHeaders(from)) // 68 | .withInjectionUrl(from.getInjectionUrl()) // 69 | .withInjectionUrlRegexp(from.getInjectionUrlRegexp()) // 70 | .withVariableName(from.getVariableName()) // 71 | .withVariableRegex(from.getVariableRegex()) // 72 | .withMethod(from.getMethod()) // 73 | .withName(from.getName()) // 74 | .withPassword(from.getPassword()) // 75 | .withPostContent(from.getPostContent()) // 76 | .withPostContentEncoding(from.getPostContentEncoding()) // 77 | .withProxyPassword(from.getProxyPassword()) // 78 | .withProxyPort(from.getProxyPort()) // 79 | .withProxyServer(from.getProxyServer()) // 80 | .withProxySchema(from.getProxySchema()) // 81 | .withProxyUser(from.getProxyUser()) // 82 | .setTriggers(toPrnfbPullRequestActions(from.getTriggers())) // 83 | .withUpdatePullRequestRefs(from.isUpdatePullRequestRefs()) // 84 | .withTriggerIfCanMerge(from.getTriggerIfCanMerge()) // 85 | .setTriggerIgnoreState(toPullRequestStates(from.getTriggerIgnoreStateList())) // 86 | .withUrl(from.getUrl()) // 87 | .withUser(from.getUser()) // 88 | .withUuid(from.getUuid()) // 89 | .withRepositorySlug(from.getRepositorySlug().orNull()) // 90 | .withProjectKey(from.getProjectKey().orNull()) // 91 | .withHttpVersion(from.getHttpVersion()) 92 | .build(); 93 | } 94 | 95 | private static List toHeaders(final List headers) { 96 | final List to = newArrayList(); 97 | if (headers != null) { 98 | for (final PrnfbHeader h : headers) { 99 | final HeaderDTO t = new HeaderDTO(); 100 | t.setName(h.getName()); 101 | t.setValue(h.getValue()); 102 | to.add(t); 103 | } 104 | } 105 | return to; 106 | } 107 | 108 | private static List toHeaders(final NotificationDTO from) { 109 | final List to = newArrayList(); 110 | if (from.getHeaders() != null) { 111 | for (final HeaderDTO headerDto : from.getHeaders()) { 112 | if (!isNullOrEmpty(headerDto.getName()) && !isNullOrEmpty(headerDto.getValue())) { 113 | to.add(new PrnfbHeader(headerDto.getName(), headerDto.getValue())); 114 | } 115 | } 116 | } 117 | return to; 118 | } 119 | 120 | private static List toPrnfbPullRequestActions( 121 | final List strings) { 122 | final List to = newArrayList(); 123 | if (strings != null) { 124 | for (final String from : strings) { 125 | to.add(PrnfbPullRequestAction.valueOf(from)); 126 | } 127 | } 128 | return to; 129 | } 130 | 131 | private static List toPullRequestStates(final List strings) { 132 | final List to = newArrayList(); 133 | if (strings != null) { 134 | for (final String from : strings) { 135 | to.add(PullRequestState.valueOf(from)); 136 | } 137 | } 138 | return to; 139 | } 140 | 141 | private static List toPullRequestStateStrings(final List list) { 142 | final List to = newArrayList(); 143 | if (list != null) { 144 | for (final Enum e : list) { 145 | to.add(e.name()); 146 | } 147 | } 148 | return to; 149 | } 150 | 151 | private static List toStrings(final List list) { 152 | final List to = newArrayList(); 153 | if (list != null) { 154 | for (final Enum e : list) { 155 | to.add(e.name()); 156 | } 157 | } 158 | return to; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/se/bjurr/prnfb/transformer/SettingsTransformer.java: -------------------------------------------------------------------------------- 1 | package se.bjurr.prnfb.transformer; 2 | 3 | import static se.bjurr.prnfb.settings.PrnfbSettings.UNCHANGED; 4 | import static se.bjurr.prnfb.settings.PrnfbSettingsDataBuilder.prnfbSettingsDataBuilder; 5 | 6 | import se.bjurr.prnfb.presentation.dto.SettingsDataDTO; 7 | import se.bjurr.prnfb.settings.PrnfbSettingsData; 8 | 9 | public class SettingsTransformer { 10 | 11 | public static SettingsDataDTO toDto(PrnfbSettingsData settingsData) { 12 | SettingsDataDTO dto = new SettingsDataDTO(); 13 | dto.setAdminRestriction(settingsData.getAdminRestriction()); 14 | dto.setKeyStore(settingsData.getKeyStore().orNull()); 15 | dto.setKeyStorePassword(UNCHANGED); 16 | dto.setKeyStoreType(settingsData.getKeyStoreType()); 17 | dto.setShouldAcceptAnyCertificate(settingsData.isShouldAcceptAnyCertificate()); 18 | return dto; 19 | } 20 | 21 | public static PrnfbSettingsData toPrnfbSettingsData(SettingsDataDTO settingsDataDto) { 22 | return prnfbSettingsDataBuilder() // 23 | .setAdminRestriction(settingsDataDto.getAdminRestriction()) // 24 | .setKeyStore(settingsDataDto.getKeyStore()) // 25 | .setKeyStorePassword(settingsDataDto.getKeyStorePassword()) // 26 | .setKeyStoreType(settingsDataDto.getKeyStoreType()) // 27 | .setShouldAcceptAnyCertificate(settingsDataDto.isShouldAcceptAnyCertificate()) // 28 | .build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/admin.css: -------------------------------------------------------------------------------- 1 | .aui-flatpack-example { 2 | border: 1px solid #ccc; 3 | border-radius: 3px; 4 | margin: 10px 0 20px 0; 5 | padding: 20px; 6 | } 7 | 8 | .statusresponse { 9 | margin: 10px; 10 | } 11 | 12 | .textarea { 13 | width: 400px; 14 | max-width: 400px !important; 15 | } -------------------------------------------------------------------------------- /src/main/resources/admin.js: -------------------------------------------------------------------------------- 1 | define('plugin/prnfb/admin', [ 2 | 'jquery', 3 | '@atlassian/aui', 4 | 'plugin/prnfb/utils' 5 | ], function($, AJS, utils) { 6 | var settingsAdminUrlPostUrl = "/rest/prnfb-admin/1.0/settings"; 7 | var settingsAdminUrl = settingsAdminUrlPostUrl; 8 | 9 | var notificationsAdminUrlPostUrl = "/rest/prnfb-admin/1.0/settings/notifications"; 10 | var notificationsAdminUrl = notificationsAdminUrlPostUrl; 11 | 12 | var buttonsAdminUrlPostUrl = "/rest/prnfb-admin/1.0/settings/buttons"; 13 | var buttonsAdminUrl = buttonsAdminUrlPostUrl; 14 | 15 | var projectKey; 16 | if ($('#prnfbRepositorySlug').length !== 0) { 17 | projectKey = $('#prnfbProjectKey').val(); 18 | var repositorySlug = $('#prnfbRepositorySlug').val(); 19 | 20 | notificationsAdminUrl = notificationsAdminUrlPostUrl + '/projectKey/' + projectKey + '/repositorySlug/' + repositorySlug; 21 | buttonsAdminUrl = buttonsAdminUrlPostUrl + '/projectKey/' + projectKey + '/repositorySlug/' + repositorySlug; 22 | } else if ($('#prnfbProjectKey').length !== 0) { 23 | projectKey = $('#prnfbProjectKey').val(); 24 | 25 | notificationsAdminUrl = notificationsAdminUrlPostUrl + '/projectKey/' + projectKey; 26 | buttonsAdminUrl = buttonsAdminUrlPostUrl + '/projectKey/' + projectKey; 27 | } 28 | 29 | $(document) 30 | .ajaxStart(function() { 31 | $('.prnfb button').attr('aria-disabled', 'true'); 32 | }) 33 | .ajaxStop(function() { 34 | $('.prnfb button').attr('aria-disabled', 'false'); 35 | }); 36 | 37 | $(document).ready(function() { 38 | utils.setupForm('#prnfbsettingsadmin', settingsAdminUrl, settingsAdminUrlPostUrl); 39 | utils.setupForms('#prnfbbuttonadmin', buttonsAdminUrl, buttonsAdminUrlPostUrl); 40 | utils.setupForms('#prnfbnotificationadmin', notificationsAdminUrl, notificationsAdminUrlPostUrl); 41 | }); 42 | }); 43 | 44 | AJS.$(document).ready(function() { 45 | require('plugin/prnfb/admin'); 46 | }); 47 | -------------------------------------------------------------------------------- /src/main/resources/atlassian-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ${project.description} 4 | ${project.version} 5 | 6 | true 7 | images/pluginIcon.png 8 | images/pluginLogo.png 9 | /plugins/servlet/prnfb/admin 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Provides REST resources for the admin UI. 32 | 33 | 34 | 35 | com.atlassian.auiplugin:ajs 36 | com.atlassian.auiplugin:template 37 | com.atlassian.bitbucket.server.bitbucket-web-api:server 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | /prnfb/admin* 46 | 47 | 48 | 49 | 50 | 51 | /plugins/servlet/prnfb/admin/${repository.project.key}/${repository.slug} 52 | 53 | 54 | 55 | 56 | 57 | /plugins/servlet/prnfb/admin/${project.key} 58 | 59 | 60 | 61 | 62 | 64 | 65 | 67 | 68 | 70 | 71 | 73 | 74 | 76 | 77 | 79 | 80 | 82 | 83 | 85 | 86 | 88 | 89 | 91 | 92 | 93 | 94 | 95 | 96 | com.atlassian.bitbucket.bitbucket-web-plugin:global 97 | com.atlassian.auiplugin:ajs 98 | com.atlassian.bitbucket.server.bitbucket-web-api:server 99 | bitbucket.ui.pullrequest.action 100 | 101 | -------------------------------------------------------------------------------- /src/main/resources/images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasbjerre/pull-request-notifier-for-bitbucket/c3043df6f5477a4fc0db0167f072d91ffb462a6a/src/main/resources/images/banner.png -------------------------------------------------------------------------------- /src/main/resources/images/pluginIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasbjerre/pull-request-notifier-for-bitbucket/c3043df6f5477a4fc0db0167f072d91ffb462a6a/src/main/resources/images/pluginIcon.png -------------------------------------------------------------------------------- /src/main/resources/images/pluginLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasbjerre/pull-request-notifier-for-bitbucket/c3043df6f5477a4fc0db0167f072d91ffb462a6a/src/main/resources/images/pluginLogo.png -------------------------------------------------------------------------------- /src/main/resources/prnfs.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomasbjerre/pull-request-notifier-for-bitbucket/c3043df6f5477a4fc0db0167f072d91ffb462a6a/src/main/resources/prnfs.properties -------------------------------------------------------------------------------- /src/main/resources/utils.js: -------------------------------------------------------------------------------- 1 | define('plugin/prnfb/utils', [ 2 | 'jquery', 3 | 'plugin/prnfb/3rdparty', 4 | 'bitbucket/util/server' 5 | ], function($, trdparty, srv) { 6 | 7 | function postForm(url, formSelector, whenDone) { 8 | $('.statusresponse').empty(); 9 | var jsonString = $(formSelector).serializeJSON(); 10 | srv.ajax({ 11 | url: url, 12 | type: "POST", 13 | contentType: "application/json; charset=utf-8", 14 | dataType: "json", 15 | data: jsonString, 16 | success: function(data) { 17 | whenDone(function() { 18 | if (data && data.uuid) { 19 | doSetupForm(formSelector, url + '/' + data.uuid); 20 | } 21 | AJS.flag({ 22 | close: 'auto', 23 | type: 'success', 24 | title: 'Saved', 25 | body: '

=)

' 26 | }); 27 | }); 28 | }, 29 | error: function(xhr, status, error) { 30 | AJS.messages.error(".statusresponse", { 31 | title: 'Error', 32 | body: '

' + 33 | 'Sent POST ' + url + ':
' + jsonString.replace(/