master
CMX\tiburon 2019-07-24 11:42:07 +03:00
parent 7f6426594e
commit 0e63e463c4
17 changed files with 1653 additions and 1 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/.settings
/.vscode
/target
/.classpath
/.project

26
LICENSE Normal file
View File

@ -0,0 +1,26 @@
Copyright (c) 2018, ASERVO Software GmbH
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of ASERVO Software GmbH nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

280
README.md
View File

@ -1,2 +1,280 @@
# jira-rest-extender
[![OCTOPUS CodeWare](https://www.tiburon.su/files/logo_octo_text_600_153.png)](https://www.tiburon.su)
REST API Extender for JIRA
================
REST API for automated JIRA configuration with URMS
Related Documentation
---------------------
* [Atlassian REST API design guidelines version 1](https://developer.atlassian.com/server/framework/atlassian-sdk/atlassian-rest-api-design-guidelines-version-1/)
Resources
---------
All resources produce JSON (media type: `application/json`) results.
### Settings
Access important JIRA settings like the title, the base url, the mode
etc.
* #### `GET /rest/extender/1/settings`
Get JIRA application settings.
__Responses__
![Status 200][status-200]
```javascript
{
"baseurl": "http://localhost:2990/jira",
"mode": "private",
"title": "Your Company JIRA"
}
```
![Status 401][status-401]
Returned if the current user is not authenticated.
![Status 403][status-403]
Returned if the current user is not an administrator.
* #### `PUT /rest/extender/1/settings`
Set JIRA application settings.
__Request Body__
Media type: `application/json`
Content: Settings, for example:
```javascript
{
"baseurl": "http://localhost:2990/jira",
"mode": "private",
"title": "Your Company JIRA"
}
```
__Request Parameters__
None.
__Responses__
![Status 200][status-200]
Returned if request could be executed without major exceptions.
The response will contain a list of errors that occurred while setting
some specific values such as a string that was too long, for example:
```
{
"errorMessages": [
"The length of the application title must not exceed 255 characters"
],
"errors": {}
}
```
![Status 401][status-401]
Returned if the current user is not authenticated.
![Status 403][status-403]
Returned if the current user is not an administrator.
### Licenses
The JIRA license API is a bit weird and needs to be well understood.
Just like in the web interface, different license keys can theoretically
be set for each application (JIRA Core, JIRA Software, etc.). However,
the entered license key is always set for all applications for which it
is valid.
For example:
1. JIRA Core and JIRA Software each have their own license key. If a
license key is now entered (in the web interface or via the REST API)
with which both applications can be licensed, this license key is stored
for both applications.
2. JIRA Core and JIRA Software use a common license key. If a license
key is now entered (in the web interface or via the REST API) with which
only JIRA Core can be licensed, the license key is also only stored for
JIRA Core.
So again, an entered license key is always stored for all applications
for which it is valid. The web interface might suggest that you can
select the desired application, but this is not true.
* #### `GET /rest/extender/1/licenses`
Get the license keys together with the application keys of the
applications using this license key.
__Responses__
![Status 200][status-200]
```javascript
{
"licenses": [
{
"key": "AAA...",
"applicationKeys": [
"jira-software"
]
},
{
"key": "AAA...",
"applicationKeys": [
"jira-core",
"jira-servicedesk"
]
}
]
}
```
![Status 401][status-401]
Returned if the current user is not authenticated.
![Status 403][status-403]
Returned if the current user is not an administrator.
* #### `PUT /rest/extender/1/licenses`
Set a license by its license key.
__Request Body__
Media type: `text/plain`
Content: License key, for example:
```
AAA...
```
__Request Parameters__
| parameter | type | description |
| ----------- | --------- | ------------------------------------------------------------------------------ |
| `clear` | _boolean_ | Clear all licenses before setting the new license, optional, defaults to false |
__Responses__
![Status 200][status-200]
```javascript
{
"key": "AAA...",
"applicationKeys": [
"jira-core",
"jira-servicedesk",
"jira-software"
]
}
```
![Status 401][status-401]
Returned if the current user is not authenticated.
![Status 403][status-403]
Returned if the current user is not an administrator.
### Syncronise Crowd User Directory
* #### `GET /rest/extender/1/directory`
Get info about User Directories.
__Responses__
![Status 200][status-200]
```javascript
[
{
"id": 1,
"name": "Jira Internal Directory",
"active": true,
"createdDate": 1362038271308,
"updatedDate": 1362038271308,
"lowerName": "jira internal directory",
"description": "Jira default internal directory",
"type": "INTERNAL",
"implementationClass": "com.atlassian.crowd.directory.InternalDirectory",
"lowerImplementationClass": "com.atlassian.crowd.directory.internaldirectory",
"allowedOperations":["CREATE_GROUP", "CREATE_ROLE", "CREATE_USER", "DELETE_GROUP", "DELETE_ROLE", "DELETE_USER", "UPDATE_GROUP",…],
"attributes":{
"user_encryption_method": "atlassian-security"
},
"empty": false,
"keys":["user_encryption_method"],
"encryptionType": "atlassian-security"
}
]
```
![Status 401][status-401]
Returned if the current user is not authenticated.
![Status 403][status-403]
Returned if the current user is not an administrator.
* #### `PUT /rest/extender/1/directory`
Start sycronyse User Directory.
__Request Parameters__
| parameter | type | description |
| --------- | ------ | ----------------------------------------------------- |
| `id` | _long_ | id of the User Directory, which should be syncronised |
__Responses__
![Status 200][status-200]
Returned if request could be executed without major exceptions.
The response will contain a list of errors that occurred while setting
some specific values such as a string that was too long, for example:
```
{
"message": "parameter should not be null!",
"status-code": 500,
"stack-trace": full_stack_trace_of_the_application
}
```
![Status 401][status-401]
Returned if the current user is not authenticated.
![Status 403][status-403]
Returned if the current user is not an administrator.
[status-200]: https://img.shields.io/badge/status-200-brightgreen.svg
[status-400]: https://img.shields.io/badge/status-400-red.svg
[status-401]: https://img.shields.io/badge/status-401-red.svg
[status-403]: https://img.shields.io/badge/status-403-red.svg
[status-404]: https://img.shields.io/badge/status-404-red.svg

533
pom.xml Normal file
View File

@ -0,0 +1,533 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>su.tiburon.atlassian.jira</groupId>
<artifactId>rest-extender</artifactId>
<version>1.1.0</version>
<packaging>atlassian-plugin</packaging>
<name>REST API extender for JIRA</name>
<description>REST API extender for JIRA.</description>
<licenses>
<license>
<name>BSD 3-Clause License</name>
<url>https://opensource.org/licenses/BSD-3-Clause</url>
</license>
</licenses>
<issueManagement>
<system>Github</system>
<url>https://github.com/tiburon-777/rest-extender/issues</url>
</issueManagement>
<scm>
<connection>scm:git:git://github.com/tiburon-777/rest-extender.git</connection>
<developerConnection>scm:git:git@github.com:tiburon-777/rest-extender.git</developerConnection>
<url>https://github.com/tiburon-777/rest-extender</url>
</scm>
<organization>
<name>OCTOPUS CodeWare</name>
<url>http://www.tiburon.su/</url>
</organization>
<developers>
<developer>
<name>Andrey Ivanov</name>
<email>ya@tiburon.su</email>
<organization>OCTOPUS CodeWare</organization>
<organizationUrl>https://www.tiburon.su</organizationUrl>
</developer>
</developers>
<properties>
<!-- General properties -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Atlassian properties -->
<jira.version>8.0.1</jira.version>
<!-- Be careful with changing the data version -->
<jira.data.version>7.5.0</jira.data.version>
<atlassian.amps.version>8.0.2</atlassian.amps.version>
<amps.version>8.0.2</amps.version>
<atlassian.project.templates.version>6.2.0</atlassian.project.templates.version>
<atlassian.sal.version>3.1.0</atlassian.sal.version>
<atlassian.plugins.rest.version>5.0.1</atlassian.plugins.rest.version>
<atlassian.quick.reload.version>2.0.0</atlassian.quick.reload.version>
<atlassian.spring.scanner.version>2.1.5</atlassian.spring.scanner.version>
<atlassian.crowdapi.version>2.10.2-rc04</atlassian.crowdapi.version>
<upm.license.compatibility.version>2.15.3</upm.license.compatibility.version>
<!-- Other properties -->
<slf4j.version>1.7.9</slf4j.version>
<javax.servlet.version>2.4</javax.servlet.version>
<javax.ws.version>1.1.1</javax.ws.version>
<javax.xml.version>2.1</javax.xml.version>
<!-- Test properties -->
<junit.version>4.12</junit.version>
<mockito.version>1.10.19</mockito.version>
<jacoco.version>0.8.1</jacoco.version>
<coveralls.version>4.3.0</coveralls.version>
</properties>
<dependencies>
<!-- Atlassian dependencies -->
<dependency>
<groupId>com.atlassian.jira</groupId>
<artifactId>jira-api</artifactId>
<version>${jira.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>jta</groupId>
<artifactId>jta</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.atlassian.jira</groupId>
<artifactId>jira-core</artifactId>
<version>${jira.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>jndi</groupId>
<artifactId>jndi</artifactId>
</exclusion>
<exclusion>
<groupId>jta</groupId>
<artifactId>jta</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.atlassian.jira.plugins</groupId>
<artifactId>project-templates-api</artifactId>
<version>${atlassian.project.templates.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.jira</groupId>
<artifactId>jira-rest-api</artifactId>
<version>${jira.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.plugins.rest</groupId>
<artifactId>atlassian-rest-common</artifactId>
<version>${atlassian.plugins.rest.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.plugins.rest</groupId>
<artifactId>atlassian-rest-doclet</artifactId>
<version>${atlassian.plugins.rest.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.plugin</groupId>
<artifactId>atlassian-spring-scanner-annotation</artifactId>
<version>${atlassian.spring.scanner.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.templaterenderer</groupId>
<artifactId>atlassian-template-renderer-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.sal</groupId>
<artifactId>sal-api</artifactId>
<version>${atlassian.sal.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.crowd</groupId>
<artifactId>crowd-api</artifactId>
<version>${atlassian.crowdapi.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.upm</groupId>
<artifactId>plugin-license-storage-lib</artifactId>
<version>${upm.license.compatibility.version}</version>
</dependency>
<dependency>
<groupId>com.atlassian.upm</groupId>
<artifactId>plugin-license-storage-plugin</artifactId>
<version>${upm.license.compatibility.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.upm</groupId>
<artifactId>licensing-api</artifactId>
<version>${upm.license.compatibility.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.upm</groupId>
<artifactId>upm-api</artifactId>
<version>${upm.license.compatibility.version}</version>
<scope>provided</scope>
</dependency>
<!-- Other dependencies -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>${javax.ws.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${javax.xml.version}</version>
<scope>provided</scope>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.atlassian.jira</groupId>
<artifactId>jira-tests</artifactId>
<version>${jira.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.atlassian.maven.plugins</groupId>
<artifactId>jira-maven-plugin</artifactId>
<version>${atlassian.amps.version}</version>
<extensions>true</extensions>
<configuration>
<productVersion>${jira.version}</productVersion>
<enableFastdev>false</enableFastdev>
<enableDevToolbox>false</enableDevToolbox>
<enablePde>false</enablePde>
<!-- <extractDependencies>false</extractDependencies> -->
<skipRestDocGeneration>true</skipRestDocGeneration>
<skipManifestValidation>true</skipManifestValidation>
<allowGoogleTracking>false</allowGoogleTracking>
<systemPropertyVariables>
<atlassian.mail.senddisabled>false</atlassian.mail.senddisabled>
</systemPropertyVariables>
<applications>
<application>
<applicationKey>jira-software</applicationKey>
<version>${jira.version}</version>
</application>
</applications>
<pluginArtifacts>
<pluginArtifact>
<groupId>com.atlassian.labs.plugins</groupId>
<artifactId>quickreload</artifactId>
<version>${atlassian.quick.reload.version}</version>
</pluginArtifact>
</pluginArtifacts>
<instructions>
<Atlassian-Plugin-Key>${project.groupId}.${project.artifactId}</Atlassian-Plugin-Key>
<Spring-Context>*</Spring-Context>
<Import-Package>
org.springframework.*;resolution:="optional",
org.eclipse.gemini.*;resolution:="optional",
sun.misc;resolution:=optional,
*
</Import-Package>
<Export-Package/>
</instructions>
<products>
<product>
<id>crowd</id>
<instanceId>crowd</instanceId>
<version>3.1.3</version>
</product>
<product>
<id>confluence</id>
<instanceId>confluence</instanceId>
<version>6.8.0</version>
</product>
</products>
</configuration>
</plugin>
<plugin>
<groupId>com.atlassian.plugin</groupId>
<artifactId>atlassian-spring-scanner-maven-plugin</artifactId>
<version>${atlassian.spring.scanner.version}</version>
<executions>
<execution>
<goals>
<goal>atlassian-spring-scanner</goal>
</goals>
<phase>process-classes</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>${coveralls.version}</version>
</plugin>
</plugins>
</build>
<distributionManagement>
<snapshotRepository>
<id>sonatype-nexus-snapshots</id>
<name>Sonatype Nexus snapshot repository</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>sonatype-nexus-staging</id>
<name>Sonatype Nexus release staging repository</name>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<profiles>
<profile>
<id>release</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>deploy</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
<configuration>
<keyname>${gpg.keyname}</keyname>
<passphrase>${gpg.passphrase}</passphrase>
<executable>${gpg.executable}</executable>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
<configuration>
<!-- Deployment is already handled by nexus-staging-maven-plugin -->
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.8</version>
<extensions>true</extensions>
<executions>
<execution>
<id>default-deploy</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
</execution>
</executions>
<configuration>
<serverId>sonatype-nexus-staging</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>sources</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>install</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>javadoc</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>install</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>atlassian-public</id>
<url>https://packages.atlassian.com/maven/repository/public</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
</releases>
</repository>
<repository>
<id>sonatype-oss</id>
<url>https://oss.sonatype.org/content/groups/public</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>atlassian-public</id>
<url>https://packages.atlassian.com/maven/repository/public</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
</releases>
</pluginRepository>
<pluginRepository>
<id>sonatype-oss</id>
<url>https://oss.sonatype.org/content/groups/public</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
</releases>
</pluginRepository>
</pluginRepositories>
</project>

View File

@ -0,0 +1,160 @@
package su.tiburon.atlassian.jira;
import com.atlassian.jira.bc.license.JiraLicenseService;
import com.atlassian.jira.bc.license.JiraLicenseService.ValidationResult;
import com.atlassian.jira.config.properties.APKeys;
import com.atlassian.jira.config.properties.ApplicationProperties;
import com.atlassian.jira.license.JiraLicenseManager;
import com.atlassian.jira.license.LicenseDetails;
import com.atlassian.jira.util.UrlValidator;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.google.common.collect.Lists;
import com.opensymphony.util.TextUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import com.atlassian.crowd.embedded.api.CrowdDirectoryService;
import com.atlassian.crowd.embedded.api.Directory;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.util.Collection;
import java.util.List;
@Component
public class JiraApplicationHelper {
@ComponentImport
private final ApplicationProperties applicationProperties;
private final JiraI18nHelper i18nHelper;
@ComponentImport
private final JiraLicenseManager licenseManager;
@ComponentImport
private final JiraLicenseService licenseService;
@ComponentImport
private final CrowdDirectoryService crowdDirectoryService;
/**
* Constructor.
*
* @param applicationProperties injected {@link ApplicationProperties}
* @param i18nHelper injected {@link JiraI18nHelper}
* @param licenseManager injected {@link JiraLicenseManager}
* @param licenseService injected {@link JiraLicenseService}
* @param crowdDirectoryService injected {@link CrowdDirectoryService}
*/
@Inject
public JiraApplicationHelper(
final ApplicationProperties applicationProperties,
final JiraI18nHelper i18nHelper,
final JiraLicenseManager licenseManager,
final JiraLicenseService licenseService,
final CrowdDirectoryService crowdDirectoryService) {
this.applicationProperties = applicationProperties;
this.i18nHelper = i18nHelper;
this.licenseManager = licenseManager;
this.licenseService = licenseService;
this.crowdDirectoryService = crowdDirectoryService;
}
public void syncDir(@Nonnull final Long dirId ) {
crowdDirectoryService.synchroniseDirectory(dirId);
}
public List<Directory> getDir() {
return crowdDirectoryService.findAllDirectories();
}
public String getBaseUrl() {
return applicationProperties.getString(APKeys.JIRA_BASEURL);
}
public void setBaseUrl(
@Nonnull final String baseUrl) {
if (!UrlValidator.isValid(baseUrl)) {
throw new IllegalArgumentException(i18nHelper.getText("admin.errors.you.must.set.a.valid.base.url"));
}
applicationProperties.setString(APKeys.JIRA_BASEURL, baseUrl);
}
public String getMode() {
return applicationProperties.getString(APKeys.JIRA_MODE);
}
public void setMode(
@Nonnull final String mode) {
if (!mode.equalsIgnoreCase("public") && !mode.equalsIgnoreCase("private")) {
throw new IllegalArgumentException("Invalid mode");
}
if (mode.equalsIgnoreCase("public") && hasExternalUserManagement()) {
throw new IllegalArgumentException(i18nHelper.getText("admin.errors.invalid.mode.externalUM.combination"));
}
applicationProperties.setString(APKeys.JIRA_MODE, mode);
}
public String getTitle() {
return applicationProperties.getString(APKeys.JIRA_TITLE);
}
public void setTitle(
@Nonnull final String title) {
if (!TextUtils.stringSet(title)) {
throw new IllegalArgumentException(i18nHelper.getText("admin.errors.you.must.set.an.application.title"));
}
if (StringUtils.length(title) > 255) {
throw new IllegalArgumentException(i18nHelper.getText("admin.errors.invalid.length.of.an.application.title"));
}
applicationProperties.setString(APKeys.JIRA_TITLE, title);
}
private boolean hasExternalUserManagement() {
return applicationProperties.getOption(APKeys.JIRA_OPTION_USER_EXTERNALMGT);
}
/**
* Get all licenses.
*
* @return licenses details
*/
public Collection<LicenseDetails> getLicenses() {
return Lists.newArrayList(licenseManager.getLicenses());
}
/**
* Set a new license key and clear all licenses before if wanted.
*
* @param key the license key
* @param clear whether to remove all licenses before setting the new license
* @return license details
*/
public LicenseDetails setLicense(
final String key,
boolean clear) {
final ValidationResult validationResult = licenseService.validate(i18nHelper.getI18nHelper(), key);
if (validationResult.getErrorCollection().hasAnyErrors()) {
throw new IllegalArgumentException("Specified license was invalid.");
}
final String licenseString = validationResult.getLicenseString();
return clear
? licenseManager.clearAndSetLicenseNoEvent(licenseString)
: licenseManager.setLicenseNoEvent(licenseString);
}
}

View File

@ -0,0 +1,59 @@
package su.tiburon.atlassian.jira;
import com.atlassian.jira.user.preferences.PreferenceKeys;
import com.atlassian.jira.util.I18nHelper;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.opensymphony.module.propertyset.PropertySet;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import java.util.Locale;
@Component
public class JiraI18nHelper {
@ComponentImport
private final I18nHelper.BeanFactory i18nBeanFactory;
private final JiraUserHelper userHelper;
/**
* Constructor.
*
* @param i18nBeanFactory injected {@link I18nHelper.BeanFactory}
* @param userHelper injected {@link JiraUserHelper}
*/
@Inject
public JiraI18nHelper(
final I18nHelper.BeanFactory i18nBeanFactory,
final JiraUserHelper userHelper) {
this.i18nBeanFactory = i18nBeanFactory;
this.userHelper = userHelper;
}
public String getText(
final String key) {
return getI18nHelper().getText(key);
}
public I18nHelper getI18nHelper() {
return i18nBeanFactory.getInstance(getLocale());
}
Locale getLocale() {
final PropertySet userProperties = userHelper.getUserProperties();
if (userProperties != null) {
final String locale = userProperties.getString(PreferenceKeys.USER_LOCALE);
if (locale != null) {
return new Locale(locale);
}
}
return Locale.getDefault();
}
}

View File

@ -0,0 +1,75 @@
package su.tiburon.atlassian.jira;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.user.ApplicationUser;
import com.atlassian.jira.user.UserPropertyManager;
import com.atlassian.jira.user.preferences.UserPreferencesManager;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.opensymphony.module.propertyset.PropertySet;
import org.springframework.stereotype.Component;
import javax.annotation.Nullable;
import javax.inject.Inject;
@Component
public class JiraUserHelper {
@ComponentImport
private final JiraAuthenticationContext authenticationContext;
@ComponentImport
private final UserPropertyManager userPropertyManager;
/**
* Constructor.
*
* @param authenticationContext injected {@link JiraAuthenticationContext}
* @param userPropertyManager injected {@link UserPreferencesManager}
*/
@Inject
public JiraUserHelper(
final JiraAuthenticationContext authenticationContext,
final UserPropertyManager userPropertyManager) {
this.authenticationContext = authenticationContext;
this.userPropertyManager = userPropertyManager;
}
/**
* Get logged in user.
*
* @return user
*/
@Nullable
public ApplicationUser getLoggedInUser() {
return authenticationContext.getLoggedInUser();
}
/**
* Get the property set of the logged in user.
*
* @return property set
*/
@Nullable
public PropertySet getUserProperties() {
return getUserProperties(getLoggedInUser());
}
/**
* Get the property set of the given user.
*
* @param user the application user
* @return property set
*/
@Nullable
public PropertySet getUserProperties(
@Nullable final ApplicationUser user) {
if (user != null) {
return userPropertyManager.getPropertySet(user);
}
return null;
}
}

View File

@ -0,0 +1,48 @@
package su.tiburon.atlassian.jira;
import com.atlassian.jira.security.GlobalPermissionManager;
import com.atlassian.jira.user.ApplicationUser;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import static com.atlassian.jira.permission.GlobalPermissionKey.SYSTEM_ADMIN;
@Component
public class JiraWebAuthenticationHelper {
@ComponentImport
private final GlobalPermissionManager globalPermissionManager;
private final JiraUserHelper userHelper;
/**
* Constructor.
*
* @param userHelper the injected {@link JiraUserHelper}
* @param globalPermissionManager the injected {@link GlobalPermissionManager}
*/
@Inject
public JiraWebAuthenticationHelper(
final GlobalPermissionManager globalPermissionManager,
final JiraUserHelper userHelper) {
this.globalPermissionManager = globalPermissionManager;
this.userHelper = userHelper;
}
public void mustBeSysAdmin() {
final ApplicationUser user = userHelper.getLoggedInUser();
if (user == null) {
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
final boolean isSysAdmin = globalPermissionManager.hasPermission(SYSTEM_ADMIN, user);
if (!isSysAdmin) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
}
}

View File

@ -0,0 +1,75 @@
package su.tiburon.atlassian.jira.bean;
import com.atlassian.application.api.ApplicationKey;
import com.atlassian.jira.license.LicenseDetails;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Bean for {@link com.atlassian.jira.license.LicenseDetails} results in REST responses.
*/
@XmlRootElement(name = "license")
public class LicenseBean {
@XmlElement
private final String key;
@XmlElement
private final Collection<String> applicationKeys;
private LicenseBean(
final String key,
final Collection<String> applicationKeys) {
this.key = key;
this.applicationKeys = applicationKeys;
}
public String getKey() {
return key;
}
public Collection<ApplicationKey> getApplicationKeys() {
return applicationKeys.stream()
.map(ApplicationKey::valueOf)
.collect(Collectors.toSet());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LicenseBean) {
LicenseBean other = (LicenseBean) obj;
return Objects.equals(key, other.key)
&& Objects.equals(applicationKeys, other.applicationKeys);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(key, applicationKeys);
}
/**
* Factory method for creating a bean from {@link LicenseDetails}.
*
* @param licenseDetail the license details
*/
public static LicenseBean from(
final LicenseDetails licenseDetail) {
final String key = licenseDetail.getLicenseString();
final Collection<String> applicationKeys = licenseDetail.getLicensedApplications().getKeys().stream()
.map(ApplicationKey::value)
.collect(Collectors.toSet());
return new LicenseBean(key, applicationKeys);
}
}

View File

@ -0,0 +1,44 @@
package su.tiburon.atlassian.jira.bean;
import com.atlassian.jira.license.LicenseDetails;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Collection;
import java.util.stream.Collectors;
/**
* Bean for collection of {@link LicenseDetails} results in REST responses.
*/
@XmlRootElement(name = "licenses")
public class LicensesBean {
@XmlElement
private final Collection<LicenseBean> licenses;
private LicensesBean(
final Collection<LicenseBean> licenses) {
this.licenses = licenses;
}
public Collection<LicenseBean> getLicenses() {
return licenses;
}
/**
* Factory method for creating a bean from a collection of {@link LicenseDetails}.
*
* @param licenseDetails the license details
*/
public static LicensesBean from(
final Collection<LicenseDetails> licenseDetails) {
final Collection<LicenseBean> licenses = licenseDetails.stream()
.map(LicenseBean::from)
.collect(Collectors.toList());
return new LicensesBean(licenses);
}
}

View File

@ -0,0 +1,62 @@
package su.tiburon.atlassian.jira.bean;
import su.tiburon.atlassian.jira.JiraApplicationHelper;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Bean for setting results in REST responses.
*/
@XmlRootElement(name = "settings")
public class SettingsBean {
@XmlElement
private final String baseurl;
@XmlElement
private final String mode;
@XmlElement
private final String title;
/**
* The default constructor is needed for JSON request deserialization.
*/
public SettingsBean() {
this.baseurl = null;
this.mode = null;
this.title = null;
}
public SettingsBean(
final String baseUrl,
final String mode,
final String title) {
this.baseurl = baseUrl;
this.mode = mode;
this.title = title;
}
public SettingsBean(
final JiraApplicationHelper applicationHelper) {
this.baseurl = applicationHelper.getBaseUrl();
this.mode = applicationHelper.getMode();
this.title = applicationHelper.getTitle();
}
public String getBaseUrl() {
return baseurl;
}
public String getTitle() {
return title;
}
public String getMode() {
return mode;
}
}

View File

@ -0,0 +1,89 @@
package su.tiburon.atlassian.jira.rest;
import com.atlassian.jira.rest.api.util.ErrorCollection;
import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
import su.tiburon.atlassian.jira.JiraApplicationHelper;
import su.tiburon.atlassian.jira.JiraWebAuthenticationHelper;
import org.springframework.stereotype.Component;
import com.atlassian.crowd.embedded.api.Directory;
import org.codehaus.jackson.map.ObjectMapper;
import java.util.List;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
/**
* Settings resource to get the licenses.
*/
@Path("/directory")
@AnonymousAllowed
@Produces(MediaType.APPLICATION_JSON)
@Component
public class DirectoriesResource {
private final JiraApplicationHelper applicationHelper;
private final JiraWebAuthenticationHelper webAuthenticationHelper;
/**
* Constructor.
*
* @param applicationHelper the injected {@link JiraApplicationHelper}
* @param webAuthenticationHelper the injected {@link JiraWebAuthenticationHelper}
*/
@Inject
public DirectoriesResource(
final JiraApplicationHelper applicationHelper,
final JiraWebAuthenticationHelper webAuthenticationHelper) {
this.applicationHelper = applicationHelper;
this.webAuthenticationHelper = webAuthenticationHelper;
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response syncDir(@QueryParam("id") Long dirId) throws WebApplicationException {
webAuthenticationHelper.mustBeSysAdmin();
final ErrorCollection errorCollection = ErrorCollection.of();
if (dirId != null) {
try {
applicationHelper.syncDir(dirId);
} catch (Exception e) {
errorCollection.addErrorMessage(e.getMessage());
}
} else {
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
if (errorCollection.hasAnyErrors()) {
return Response.ok(errorCollection).build();
} else {
return Response.ok(null).build();
}
}
@GET
public Response getDir() {
webAuthenticationHelper.mustBeSysAdmin();
ObjectMapper mapper = new ObjectMapper();
final List<Directory> directories = applicationHelper.getDir();
String jsn = "";
try {
jsn = mapper.writeValueAsString(directories);
} catch (Exception e) {
e.printStackTrace();
}
return Response.ok(jsn).build();
}
}

View File

@ -0,0 +1,76 @@
package su.tiburon.atlassian.jira.rest;
import com.atlassian.jira.license.LicenseDetails;
import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
import su.tiburon.atlassian.jira.JiraApplicationHelper;
import su.tiburon.atlassian.jira.JiraWebAuthenticationHelper;
import su.tiburon.atlassian.jira.bean.LicenseBean;
import su.tiburon.atlassian.jira.bean.LicensesBean;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Collection;
/**
* Licenses resource to get the licenses.
*/
@Path("/licenses")
@AnonymousAllowed
@Produces(MediaType.APPLICATION_JSON)
@Component
public class LicensesResource {
private final JiraApplicationHelper applicationHelper;
private final JiraWebAuthenticationHelper webAuthenticationHelper;
/**
* Constructor.
*
* @param applicationHelper the injected {@link JiraApplicationHelper}
* @param webAuthenticationHelper the injected {@link JiraWebAuthenticationHelper}
*/
@Inject
public LicensesResource(
final JiraApplicationHelper applicationHelper,
final JiraWebAuthenticationHelper webAuthenticationHelper) {
this.applicationHelper = applicationHelper;
this.webAuthenticationHelper = webAuthenticationHelper;
}
@GET
public Response getLicenses() {
webAuthenticationHelper.mustBeSysAdmin();
final Collection<LicenseDetails> licenseDetails = applicationHelper.getLicenses();
return Response.ok(LicensesBean.from(licenseDetails)).build();
}
@PUT
@Consumes(MediaType.TEXT_PLAIN)
public Response setLicense(
@QueryParam("clear") @DefaultValue("false") boolean clear,
final String licenseKey) throws WebApplicationException {
webAuthenticationHelper.mustBeSysAdmin();
if (licenseKey == null) {
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
final LicenseDetails licenseDetail = applicationHelper.setLicense(licenseKey, clear);
return Response.ok(LicenseBean.from(licenseDetail)).build();
}
}

View File

@ -0,0 +1,91 @@
package su.tiburon.atlassian.jira.rest;
import com.atlassian.jira.rest.api.util.ErrorCollection;
import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
import su.tiburon.atlassian.jira.JiraApplicationHelper;
import su.tiburon.atlassian.jira.JiraWebAuthenticationHelper;
import su.tiburon.atlassian.jira.bean.SettingsBean;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* Settings resource to get the licenses.
*/
@Path("/settings")
@AnonymousAllowed
@Produces(MediaType.APPLICATION_JSON)
@Component
public class SettingsResource {
private final JiraApplicationHelper applicationHelper;
private final JiraWebAuthenticationHelper webAuthenticationHelper;
/**
* Constructor.
*
* @param applicationHelper the injected {@link JiraApplicationHelper}
* @param webAuthenticationHelper the injected {@link JiraWebAuthenticationHelper}
*/
@Inject
public SettingsResource(
final JiraApplicationHelper applicationHelper,
final JiraWebAuthenticationHelper webAuthenticationHelper) {
this.applicationHelper = applicationHelper;
this.webAuthenticationHelper = webAuthenticationHelper;
}
@GET
public Response getSettings() {
webAuthenticationHelper.mustBeSysAdmin();
final SettingsBean settingsBean = new SettingsBean(applicationHelper);
return Response.ok(settingsBean).build();
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response setSettings(
final SettingsBean settingsBean) {
webAuthenticationHelper.mustBeSysAdmin();
final ErrorCollection errorCollection = ErrorCollection.of();
if (settingsBean.getBaseUrl() != null) {
try {
applicationHelper.setBaseUrl(settingsBean.getBaseUrl());
} catch (Exception e) {
errorCollection.addErrorMessage(e.getMessage());
}
}
if (settingsBean.getMode() != null) {
try {
applicationHelper.setMode(settingsBean.getMode());
} catch (Exception e) {
errorCollection.addErrorMessage(e.getMessage());
}
}
if (settingsBean.getTitle() != null) {
try {
applicationHelper.setTitle(settingsBean.getTitle());
} catch (Exception e) {
errorCollection.addErrorMessage(e.getMessage());
}
}
return Response.ok(errorCollection).build();
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:atlassian-scanner="http://www.atlassian.com/schema/atlassian-scanner/2"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.atlassian.com/schema/atlassian-scanner/2
http://www.atlassian.com/schema/atlassian-scanner/2/atlassian-scanner.xsd">
<atlassian-scanner:scan-indexes/>
</beans>

View File

@ -0,0 +1,21 @@
<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2">
<plugin-info>
<description>${project.description}</description>
<version>${project.version}</version>
<vendor name="${project.organization.name}" url="${project.organization.url}" />
<param name="plugin-icon">images/logo_octo_375_293.png</param>
<param name="plugin-logo">images/logo_octo_375_293.png</param>
</plugin-info>
<resource type="i18n" name="i18n" location="rest-extender"/>
<web-resource key="rest-extender-web-resource" name="JIRA REST Extender Web Resource">
<dependency>com.atlassian.auiplugin:ajs</dependency>
<dependency>com.atlassian.auiplugin:aui-experimental-expander</dependency>
<resource type="download" name="images/" location="/images"/>
<context>atl.general</context>
</web-resource>
<rest key="rest-extender" name="JIRA REST Extender" path="/extender" version="1"/>
</atlassian-plugin>

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB