mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 00:15:44 +01:00
Merged 2.0.0-m3
This commit is contained in:
18
Jenkinsfile
vendored
18
Jenkinsfile
vendored
@@ -15,7 +15,7 @@ node('docker') {
|
|||||||
disableConcurrentBuilds()
|
disableConcurrentBuilds()
|
||||||
])
|
])
|
||||||
|
|
||||||
timeout(activity: true, time: 20, unit: 'MINUTES') {
|
timeout(activity: true, time: 30, unit: 'MINUTES') {
|
||||||
|
|
||||||
catchError {
|
catchError {
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ node('docker') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage('Build') {
|
stage('Build') {
|
||||||
mvn 'clean install -DskipTests'
|
mvn 'clean install -Pdoc -DskipTests'
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Unit Test') {
|
stage('Unit Test') {
|
||||||
@@ -50,6 +50,17 @@ node('docker') {
|
|||||||
def dockerImageTag = "2.0.0-dev-${commitHash.substring(0,7)}-${BUILD_NUMBER}"
|
def dockerImageTag = "2.0.0-dev-${commitHash.substring(0,7)}-${BUILD_NUMBER}"
|
||||||
|
|
||||||
if (isMainBranch()) {
|
if (isMainBranch()) {
|
||||||
|
|
||||||
|
// stage('Lifecycle') {
|
||||||
|
// nexusPolicyEvaluation iqApplication: selectedApplication('scm'), iqScanPatterns: [[scanPattern: 'scm-server/target/scm-server-app.zip']], iqStage: 'build'
|
||||||
|
// }
|
||||||
|
|
||||||
|
stage('Archive') {
|
||||||
|
archiveArtifacts 'scm-webapp/target/scm-webapp.war'
|
||||||
|
archiveArtifacts 'scm-server/target/scm-server-app.*'
|
||||||
|
archiveArtifacts 'scm-webapp/target/scm-webapp-restdocs.zip'
|
||||||
|
}
|
||||||
|
|
||||||
stage('Docker') {
|
stage('Docker') {
|
||||||
def image = docker.build('cloudogu/scm-manager')
|
def image = docker.build('cloudogu/scm-manager')
|
||||||
docker.withRegistry('', 'hub.docker.com-cesmarvin') {
|
docker.withRegistry('', 'hub.docker.com-cesmarvin') {
|
||||||
@@ -107,7 +118,8 @@ void analyzeWith(Maven mvn) {
|
|||||||
"-Dsonar.pullrequest.key=${env.CHANGE_ID} " +
|
"-Dsonar.pullrequest.key=${env.CHANGE_ID} " +
|
||||||
"-Dsonar.pullrequest.provider=bitbucketcloud " +
|
"-Dsonar.pullrequest.provider=bitbucketcloud " +
|
||||||
"-Dsonar.pullrequest.bitbucketcloud.owner=sdorra " +
|
"-Dsonar.pullrequest.bitbucketcloud.owner=sdorra " +
|
||||||
"-Dsonar.pullrequest.bitbucketcloud.repository=scm-manager "
|
"-Dsonar.pullrequest.bitbucketcloud.repository=scm-manager " +
|
||||||
|
"-Dsonar.cpd.exclusions=**/*StoreFactory.java,**/*UserPassword.js "
|
||||||
} else {
|
} else {
|
||||||
mvnArgs += " -Dsonar.branch.name=${env.BRANCH_NAME} "
|
mvnArgs += " -Dsonar.branch.name=${env.BRANCH_NAME} "
|
||||||
if (!isMainBranch()) {
|
if (!isMainBranch()) {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ data:
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
|
||||||
<--
|
<!--
|
||||||
in a container environment we only need stdout
|
in a container environment we only need stdout
|
||||||
-->
|
-->
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
|||||||
@@ -29,6 +29,17 @@ spec:
|
|||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: data
|
- name: data
|
||||||
mountPath: /data
|
mountPath: /data
|
||||||
|
{{- if .Values.plugins }}
|
||||||
|
- name: install-plugins
|
||||||
|
image: alpine:3.8
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ['sh', '/scripts/install-plugins.sh']
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts
|
||||||
|
{{- end }}
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Chart.Name }}
|
- name: {{ .Chart.Name }}
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
@@ -63,6 +74,11 @@ spec:
|
|||||||
- name: config
|
- name: config
|
||||||
configMap:
|
configMap:
|
||||||
name: {{ include "scm-manager.fullname" . }}
|
name: {{ include "scm-manager.fullname" . }}
|
||||||
|
{{- if .Values.plugins }}
|
||||||
|
- name: scripts
|
||||||
|
configMap:
|
||||||
|
name: {{ include "scm-manager.fullname" . }}-scripts
|
||||||
|
{{- end }}
|
||||||
{{- with .Values.nodeSelector }}
|
{{- with .Values.nodeSelector }}
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
{{ toYaml . | indent 8 }}
|
{{ toYaml . | indent 8 }}
|
||||||
|
|||||||
21
deployments/helm/templates/scripts.yaml
Normal file
21
deployments/helm/templates/scripts.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{{- if .Values.plugins }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "scm-manager.fullname" . }}-scripts
|
||||||
|
labels:
|
||||||
|
app: {{ include "scm-manager.name" . }}
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
release: "{{ .Release.Name }}"
|
||||||
|
heritage: "{{ .Release.Service }}"
|
||||||
|
data:
|
||||||
|
install-plugins.sh: |
|
||||||
|
#!/bin/sh
|
||||||
|
mkdir -p /data/plugins
|
||||||
|
chown 1000:1000 /data/plugins
|
||||||
|
{{ range $i, $plugin := .Values.plugins }}
|
||||||
|
# install plugin {{ $plugin.name }}
|
||||||
|
wget -O /data/plugins/{{ $plugin.name }}.smp {{ $plugin.url }}
|
||||||
|
chown 1000:1000 /data/plugins/{{ $plugin.name }}.smp
|
||||||
|
{{ end }}
|
||||||
|
{{- end }}
|
||||||
@@ -10,6 +10,10 @@ image:
|
|||||||
tag: latest
|
tag: latest
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
|
||||||
|
# plugins:
|
||||||
|
# - name: scm-review-plugin
|
||||||
|
# url: https://oss.cloudogu.com/jenkins/job/scm-manager/job/scm-manager-bitbucket/job/scm-review-plugin/job/develop/lastSuccessfulBuild/artifact/target/scm-review-plugin-2.0.0-SNAPSHOT.smp
|
||||||
|
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
fullnameOverride: ""
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
|||||||
76
docs/mercurial/clone-empty.md
Normal file
76
docs/mercurial/clone-empty.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Clone empty repository
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1efk0qxy1dj5v133hev91zwsf4;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 130.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
.
|
||||||
|
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1rsxj8u1rq9wizawhyyxok2p5;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: cmds=heads+%3Bknown+nodes%3D.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=ewyx4m53d8dajjsob6gxobne;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 42.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
0000000000000000000000000000000000000000
|
||||||
|
;
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1o0hou15jtiywsywutf30qwm8;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
.
|
||||||
|
publishing.True
|
||||||
|
```
|
||||||
117
docs/mercurial/push-bookmark.md
Normal file
117
docs/mercurial/push-bookmark.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# Push bookmark
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=7rq9vpp9svfm1sicq7h9vetmv;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 130.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
T 172.17.0.2:8080 -> 172.17.0.1:36576 [AP]
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1553csz4sf7scyvw8mqnqfirn;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 43.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
ef5993bb4abb32a0565c347844c6d939fc4f4b98
|
||||||
|
;1
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=11xa5u3nrmx8k1nar3sazg6jzh;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1p1uzcvfe1pvzh2buzo658rxw;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1mhlj3ucfzdp6ifmzoua4zwit;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: key=markone&namespace=bookmarks&new=ef5993bb4abb32a0565c347844c6d939fc4f4b98&old=.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 0.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=s4vtagb303dv1xg809wnp7e8z;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 2.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
.
|
||||||
|
1
|
||||||
|
```
|
||||||
167
docs/mercurial/push-multiple-branches-to-new.md
Normal file
167
docs/mercurial/push-multiple-branches-to-new.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# Push multiple branches to new repository
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1wu06ykfd4bcv1uv731y4hss2m;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 130.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1rajglvqx222g5nppcq3jdfk0;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 43.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
0000000000000000000000000000000000000000
|
||||||
|
;0
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=known HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: nodes=c0ceccb3b2f0f5c977ff32b9337519e5f37942c2+187ddf37e237c370514487a0bb1a226f11a780b3+b5914611f84eae14543684b2721eec88b0edac12+8b63a323606f10c86b30465570c2574eb7a3a989.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=a5vykp1f0ga2186l8v3gu6lid;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 4.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
0000
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=s8lpwqm4c2nqs9kwcg2ca6vm;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1d2qj3kynxlhvk31oli4kk7vf;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=unbundle HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: heads=686173686564+6768033e216468247bd031a0a2d9876d79818f8f.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 913.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HG10GZx...oh.U......E.1.....2q.<...s.1.YK*e#..b..{....{..%A.....
|
||||||
|
,\.....Y.XV....Q/J......`Q/.z.{...<.7....r.s.~?.?..<o....O.]....?.}..m?]z..I..u..}.a..rg..R[i.,D ...!.1..h.r.....G...M.\...J[.....+{.k...u..bL.!....F('..=Q.'......W.>5.~`..?..........O.j.0.....Ih.....!@.P... ..a
|
||||||
|
;!y..cT...]q.8Zg=...<..,.tq.*.........l........';..w^...w...-......Co..Fs.HYg...
|
||||||
|
9.F#.P......1..;......D.H.9$@.^....r:E..18...H....3..h...-.=.6l......=q .)."Yg..p\...s@.#.H.*....c8&96..2.GjJ.`.J....r...=Q1..@R.3.o{q...|.......yq.k..,cY..:[... ...S.2...VYp..c5..&.SFR.............V.d..o..........,.. A..M....k...0_.LO1..1"4.;...B....5.9.".U.m.e......]\../p..;?C..<vW.....|......F.8,....s....2.T
|
||||||
|
N. .k..>W9.........n.~o..gW...Q;..$....S..X.CN.5I].H..!.@...U..J...L.lY.../.-...6.:.Q.'...>.e'..<#3........OL}.52ra[..g*Y:Y....w...=..Z\...S.......tz..;..mf...W......&yUN.r.......4...........`..F...nT..U9................_.~..?...BwzUN.r....B.
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=163487i0ayf9s1k2ng9e1azadj;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 102.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
1
|
||||||
|
adding changesets
|
||||||
|
adding manifests
|
||||||
|
adding file changes
|
||||||
|
added 5 changesets with 3 changes to 3 files
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=a3i712yjss6t1xsxltnssq0tl;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 58.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
c0ceccb3b2f0f5c977ff32b9337519e5f37942c2.1
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: key=ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 0.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=g8cavdze42d83knmuasrlg10;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 2.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
.
|
||||||
|
1
|
||||||
|
```
|
||||||
183
docs/mercurial/push-multiple-branches.md
Normal file
183
docs/mercurial/push-multiple-branches.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# Push multiple branches
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1mvm1rxg8333iib7754ksusxc;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 130.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=58p9y9vcnz5cjs22dtw8mpwk;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 43.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
c0ceccb3b2f0f5c977ff32b9337519e5f37942c2
|
||||||
|
;0
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=v5wfwj8k4t261dp6808cdouoa;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=3pgqytfhm4za1dco9p41j9yz5;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=branchmap HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
.
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1tiz6zf7ui54e1j3d4vouxig5m;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 48.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
default c0ceccb3b2f0f5c977ff32b9337519e5f37942c2
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1augu4tc71xax1dit20dtxzkez;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=unbundle HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: heads=686173686564+95373ca7cd5371cb6c49bb755ee451d9ec585845.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 746.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HG10GZx...]H.Q...z..r.,.Y..Bw.~..c.Z&...hf.:......e.XK.X,...
|
||||||
|
,2.E1.B+...(.B"."*..z1.*......M...........93..k|..I..<...h..J_.L.9>.h..@.....op..^.....#....;.*..W....T@....!..dY....jT..A0O6.}..S.2..JPU.O6...aa...rY.VOf9.....7Ukj.&..<...z...j......%}..Jc.8c....k.."9.&".I.P.\..$.At......0..1..g.2.)<..$.. E..dn#....#.Y$3...n...5....J.e.......SNHN.q.MD..4..."I..`PF..?GH1..F..uES..Rl$47.....a........D.1...87.k.t..D..O_.3..6'cN.w.M..|@E.).X!.h*....U.B.X.....h..$.`4...
|
||||||
|
-..O.:./..oWN.....3...x.L......_[..../..k.R$.x.2..kkv.\2R....4...@.2...1Q..T
|
||||||
|
..(..m....s.Uo.......{.d.....Y....TYO...S.Pl`a5. ."N$.@...b...qJ.l.).n...1..F.Zy.....&>v;.q.....Jy..X.?.;....>U..|.....d.Y.*.q...NR.3...h.T..x..,.]...p{.^S.S...~..`..q.\j{.oCI.............K.....l9n.s......
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1e4fnqpncil9z1f7a2pya26nt7;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 102.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
1
|
||||||
|
adding changesets
|
||||||
|
adding manifests
|
||||||
|
adding file changes
|
||||||
|
added 4 changesets with 2 changes to 2 files
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=f9hvrjssniym1qe33q0u8r2m8;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 101.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
b5914611f84eae14543684b2721eec88b0edac12.1
|
||||||
|
187ddf37e237c370514487a0bb1a226f11a780b3.1
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: key=ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 0.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=z5lrut6940a650sw6x9bls8a;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 2.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
1
|
||||||
|
```
|
||||||
147
docs/mercurial/push-single-changeset.md
Normal file
147
docs/mercurial/push-single-changeset.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# Push single changeset
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=18r2i2jsba46d14ncsmcjdhaem;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 130.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Dc0ceccb3b2f0f5c977ff32b9337519e5f37942c2.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1fw0i0c5zpy281gfgha0f26git;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 43.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
0000000000000000000000000000000000000000
|
||||||
|
;0
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=dfa46uaqgf39w3jhk857oymu;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=2sk1llvrsagg33xgmwyirfpi;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=unbundle HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: heads=686173686564+6768033e216468247bd031a0a2d9876d79818f8f.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 261.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HG10GZx.c``8w.....>|=Y..h.q.....N.......%......Z....&&&.&...YZ.&.&[$.........$.%q..&%..d&.).....%*.....Y.....9z...v\..FF......
|
||||||
|
..F..\.z%.%\\.)).)
|
||||||
|
.P[....D..[un..L).nc..q.m*.H.l#C...eZJ..YJ.Q.qR...e.aJ.EjjJ.AZ..A.Q..E.1.T.'D..C....7s.}..4G........3.S.mL.0.....zk
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=hlucs5utn1ifnpehqmjpt593;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 102.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
1
|
||||||
|
adding changesets
|
||||||
|
adding manifests
|
||||||
|
adding file changes
|
||||||
|
added 1 changesets with 1 changes to 1 files
|
||||||
|
|
||||||
|
T 172.17.0.1:33206 -> 172.17.0.2:8080 [AP]
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=15xomlrxl8qja1cj47rjpqda0y;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 58.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
c0ceccb3b2f0f5c977ff32b9337519e5f37942c2.1
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: key=c0ceccb3b2f0f5c977ff32b9337519e5f37942c2&namespace=phases&new=0&old=1.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 0.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=5zrop5v8e661ipk12tvru525;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 2.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
1
|
||||||
|
```
|
||||||
191
pom.xml
191
pom.xml
@@ -86,20 +86,6 @@
|
|||||||
<url>http://maven.scm-manager.org/nexus/content/groups/public</url>
|
<url>http://maven.scm-manager.org/nexus/content/groups/public</url>
|
||||||
</repository>
|
</repository>
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>ossrh</id>
|
|
||||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
|
||||||
<snapshots>
|
|
||||||
<enabled>true</enabled>
|
|
||||||
<updatePolicy>daily</updatePolicy>
|
|
||||||
</snapshots>
|
|
||||||
</repository>
|
|
||||||
|
|
||||||
<repository>
|
|
||||||
<id>jitpack</id>
|
|
||||||
<url>https://jitpack.io</url>
|
|
||||||
</repository>
|
|
||||||
|
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<pluginRepositories>
|
<pluginRepositories>
|
||||||
@@ -142,6 +128,11 @@
|
|||||||
<artifactId>junit-vintage-engine</artifactId>
|
<artifactId>junit-vintage-engine</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit-pioneer</groupId>
|
||||||
|
<artifactId>junit-pioneer</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hamcrest</groupId>
|
<groupId>org.hamcrest</groupId>
|
||||||
<artifactId>hamcrest-core</artifactId>
|
<artifactId>hamcrest-core</artifactId>
|
||||||
@@ -160,20 +151,13 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.assertj</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>assertj-core</artifactId>
|
<artifactId>mockito-junit-jupiter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<!-- Dependency used in Jenkinsfile. Including this in maven provides code completion in Jenkinsfile. -->
|
<groupId>org.assertj</groupId>
|
||||||
<groupId>com.github.cloudogu</groupId>
|
<artifactId>assertj-core</artifactId>
|
||||||
<artifactId>ces-build-lib</artifactId>
|
|
||||||
<!-- Keep this version in sync with the one used in Jenkinsfile -->
|
|
||||||
<version>9aadeeb</version>
|
|
||||||
<!-- Don't ship this dependency with the app -->
|
|
||||||
<optional>true</optional>
|
|
||||||
<!-- Don't inherit this dependency! -->
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@@ -325,6 +309,13 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit-pioneer</groupId>
|
||||||
|
<artifactId>junit-pioneer</artifactId>
|
||||||
|
<version>0.3.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hamcrest</groupId>
|
<groupId>org.hamcrest</groupId>
|
||||||
<artifactId>hamcrest-core</artifactId>
|
<artifactId>hamcrest-core</artifactId>
|
||||||
@@ -346,12 +337,68 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-junit-jupiter</artifactId>
|
||||||
|
<version>${mockito.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.assertj</groupId>
|
<groupId>org.assertj</groupId>
|
||||||
<artifactId>assertj-core</artifactId>
|
<artifactId>assertj-core</artifactId>
|
||||||
<version>3.10.0</version>
|
<version>3.10.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- http -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.5.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- logging -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<version>${logback.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- xml -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
<version>${jaxb.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-impl</artifactId>
|
||||||
|
<version>${jaxb.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
|
<version>${jaxb.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.activation</groupId>
|
||||||
|
<artifactId>activation</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -362,7 +409,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.github.sdorra</groupId>
|
<groupId>com.github.sdorra</groupId>
|
||||||
<artifactId>buildfrontend-maven-plugin</artifactId>
|
<artifactId>buildfrontend-maven-plugin</artifactId>
|
||||||
<version>2.1.1</version>
|
<version>2.2.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
@@ -394,10 +441,11 @@
|
|||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>2.22.0</version>
|
<version>2.22.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-enforcer-plugin</artifactId>
|
<artifactId>maven-enforcer-plugin</artifactId>
|
||||||
<version>1.4.1</version>
|
<version>3.0.0-M1</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>enforce-java</id>
|
<id>enforce-java</id>
|
||||||
@@ -419,11 +467,31 @@
|
|||||||
<requireMavenVersion>
|
<requireMavenVersion>
|
||||||
<version>[3.1,)</version>
|
<version>[3.1,)</version>
|
||||||
</requireMavenVersion>
|
</requireMavenVersion>
|
||||||
|
<!--
|
||||||
|
enforce java 1.8 compatible bytecode
|
||||||
|
-->
|
||||||
|
<enforceBytecodeVersion>
|
||||||
|
<maxJdkVersion>1.8</maxJdkVersion>
|
||||||
|
<ignoreClasses>
|
||||||
|
<!--
|
||||||
|
ignore java 9 module info classes
|
||||||
|
because jaxb is compiled with java 7 expect of module-info, which is compiled with java 9
|
||||||
|
-->
|
||||||
|
<ignoreClass>module-info</ignoreClass>
|
||||||
|
</ignoreClasses>
|
||||||
|
</enforceBytecodeVersion>
|
||||||
</rules>
|
</rules>
|
||||||
<fail>true</fail>
|
<fail>true</fail>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>extra-enforcer-rules</artifactId>
|
||||||
|
<version>1.0-beta-7</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
@@ -612,9 +680,15 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-site-plugin</artifactId>
|
<artifactId>maven-site-plugin</artifactId>
|
||||||
<version>3.2</version>
|
<version>3.7</version>
|
||||||
<configuration>
|
</plugin>
|
||||||
<reportPlugins>
|
|
||||||
|
</plugins>
|
||||||
|
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<reporting>
|
||||||
|
<plugins>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
@@ -645,19 +719,13 @@
|
|||||||
<artifactId>maven-pmd-plugin</artifactId>
|
<artifactId>maven-pmd-plugin</artifactId>
|
||||||
<version>2.7.1</version>
|
<version>2.7.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<linkXref>true</linkXref>
|
|
||||||
<sourceEncoding>${project.build.sourceEncoding}</sourceEncoding>
|
<sourceEncoding>${project.build.sourceEncoding}</sourceEncoding>
|
||||||
<targetJdk>${project.build.javaLevel}</targetJdk>
|
<targetJdk>${project.build.javaLevel}</targetJdk>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
</reportPlugins>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
|
</reporting>
|
||||||
</build>
|
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
|
||||||
@@ -702,17 +770,9 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<doclet>org.jboss.apiviz.APIviz</doclet>
|
<failOnError>false</failOnError>
|
||||||
<docletArtifact>
|
|
||||||
<groupId>org.jboss.apiviz</groupId>
|
|
||||||
<artifactId>apiviz</artifactId>
|
|
||||||
<version>1.3.2.GA</version>
|
|
||||||
</docletArtifact>
|
|
||||||
<additionalparam>
|
|
||||||
-sourceclasspath ${project.build.outputDirectory}
|
|
||||||
-nopackagediagram
|
|
||||||
</additionalparam>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
@@ -746,31 +806,32 @@
|
|||||||
<junit.version>5.2.0</junit.version>
|
<junit.version>5.2.0</junit.version>
|
||||||
|
|
||||||
<!-- logging libraries -->
|
<!-- logging libraries -->
|
||||||
<slf4j.version>1.7.22</slf4j.version>
|
<slf4j.version>1.7.25</slf4j.version>
|
||||||
<logback.version>1.1.10</logback.version>
|
<logback.version>1.2.3</logback.version>
|
||||||
<servlet.version>3.0.1</servlet.version>
|
<servlet.version>3.0.1</servlet.version>
|
||||||
|
|
||||||
<jaxrs.version>2.0.1</jaxrs.version>
|
<jaxrs.version>2.1.1</jaxrs.version>
|
||||||
<resteasy.version>3.1.3.Final</resteasy.version>
|
<resteasy.version>3.6.2.Final</resteasy.version>
|
||||||
<jersey-client.version>1.19.4</jersey-client.version>
|
<jersey-client.version>1.19.4</jersey-client.version>
|
||||||
<enunciate.version>2.9.1</enunciate.version>
|
<enunciate.version>2.11.1</enunciate.version>
|
||||||
<jackson.version>2.8.6</jackson.version>
|
<jackson.version>2.9.8</jackson.version>
|
||||||
<guice.version>4.0</guice.version>
|
<guice.version>4.0</guice.version>
|
||||||
|
<jaxb.version>2.3.0</jaxb.version>
|
||||||
|
|
||||||
<!-- event bus -->
|
<!-- event bus -->
|
||||||
<legman.version>1.4.2</legman.version>
|
<legman.version>1.4.2</legman.version>
|
||||||
|
|
||||||
<!-- webserver -->
|
<!-- webserver -->
|
||||||
<jetty.version>9.2.10.v20150310</jetty.version>
|
<jetty.version>9.4.14.v20181114</jetty.version>
|
||||||
<jetty.maven.version>9.2.10.v20150310</jetty.maven.version>
|
<jetty.maven.version>9.4.14.v20181114</jetty.maven.version>
|
||||||
|
|
||||||
<!-- security libraries -->
|
<!-- security libraries -->
|
||||||
<ssp.version>1.1.0</ssp.version>
|
<ssp.version>1.2.0</ssp.version>
|
||||||
<shiro.version>1.4.0</shiro.version>
|
<shiro.version>1.4.0</shiro.version>
|
||||||
|
|
||||||
<!-- repostitory libraries -->
|
<!-- repository libraries -->
|
||||||
<jgit.version>v4.5.2.201704071617-r-scm1</jgit.version>
|
<jgit.version>v4.5.3.201708160445-r-scm1</jgit.version>
|
||||||
<svnkit.version>1.8.15-scm1</svnkit.version>
|
<svnkit.version>1.9.0-scm3</svnkit.version>
|
||||||
|
|
||||||
<!-- util libraries -->
|
<!-- util libraries -->
|
||||||
<guava.version>26.0-jre</guava.version>
|
<guava.version>26.0-jre</guava.version>
|
||||||
@@ -782,10 +843,22 @@
|
|||||||
|
|
||||||
<!-- build properties -->
|
<!-- build properties -->
|
||||||
<project.build.javaLevel>1.8</project.build.javaLevel>
|
<project.build.javaLevel>1.8</project.build.javaLevel>
|
||||||
|
<project.test.javaLevel>1.8</project.test.javaLevel>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<netbeans.hint.license>SCM-BSD</netbeans.hint.license>
|
<netbeans.hint.license>SCM-BSD</netbeans.hint.license>
|
||||||
<jdk.classifier />
|
<jdk.classifier />
|
||||||
<org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
|
<org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
|
||||||
|
|
||||||
|
<!-- Sonar exclusions -->
|
||||||
|
<!-- *StoreFactory classes are excluded because extracting the floating store parameter classes in a generic -->
|
||||||
|
<!-- common class creates runtime errors (IncompatibleClassChange) -->
|
||||||
|
<!-- *UserPassword JS files are excluded because extraction of common code would not make the code more readable -->
|
||||||
|
<sonar.cpd.exclusions>**/*StoreFactory.java,**/*UserPassword.js</sonar.cpd.exclusions>
|
||||||
|
|
||||||
|
<node.version>8.11.4</node.version>
|
||||||
|
<sonar.nodejs.executable>./scm-ui/target/frontend/buildfrontend-node/node-v${node.version}-linux-x64/bin/node</sonar.nodejs.executable>
|
||||||
|
|
||||||
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation to specify the source of an enricher.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Enrich {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source mapping class.
|
||||||
|
*
|
||||||
|
* @return source mapping class
|
||||||
|
*/
|
||||||
|
Class<?> value();
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.CipherInputStream;
|
||||||
|
import javax.crypto.CipherOutputStream;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link CipherStreamHandler} which uses AES. This version is used since version 1.60 for the
|
||||||
|
* cli client encryption.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
public class AesCipherStreamHandler implements CipherStreamHandler {
|
||||||
|
|
||||||
|
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5PADDING";
|
||||||
|
private static final String SECRET_KEY_ALGORITHM = "AES";
|
||||||
|
private static final int IV_LENGTH = 16;
|
||||||
|
|
||||||
|
private final SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
private final byte[] secretKey;
|
||||||
|
|
||||||
|
AesCipherStreamHandler(String secretKey) {
|
||||||
|
this.secretKey = secretKey.getBytes(Charsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream encrypt(OutputStream outputStream) throws IOException {
|
||||||
|
Cipher cipher = createCipherForEncryption();
|
||||||
|
outputStream.write(cipher.getIV());
|
||||||
|
return new CipherOutputStream(outputStream, cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream decrypt(InputStream inputStream) throws IOException {
|
||||||
|
Cipher cipher = createCipherForDecryption(inputStream);
|
||||||
|
return new CipherInputStream(inputStream, cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher createCipherForDecryption(InputStream inputStream) throws IOException {
|
||||||
|
byte[] iv = createEmptyIvArray();
|
||||||
|
inputStream.read(iv);
|
||||||
|
return createCipher(Cipher.DECRYPT_MODE, iv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] createEmptyIvArray() {
|
||||||
|
return new byte[IV_LENGTH];
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher createCipherForEncryption() {
|
||||||
|
byte[] iv = generateIV();
|
||||||
|
return createCipher(Cipher.ENCRYPT_MODE, iv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] generateIV() {
|
||||||
|
// use 12 byte as described at nist
|
||||||
|
// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||||
|
byte[] iv = createEmptyIvArray();
|
||||||
|
random.nextBytes(iv);
|
||||||
|
return iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher createCipher(int mode, byte[] iv) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||||
|
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
|
||||||
|
cipher.init(mode, new SecretKeySpec(secretKey, SECRET_KEY_ALGORITHM), ivParameterSpec);
|
||||||
|
return cipher;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new ScmConfigException("failed to create cipher", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CipherStreamHandler is able to encrypt and decrypt streams.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
public interface CipherStreamHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the given input stream.
|
||||||
|
*
|
||||||
|
* @param inputStream encrypted input stream
|
||||||
|
*
|
||||||
|
* @return raw input stream
|
||||||
|
*/
|
||||||
|
InputStream decrypt(InputStream inputStream) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts the given output stream.
|
||||||
|
*
|
||||||
|
* @param outputStream raw output stream
|
||||||
|
*
|
||||||
|
* @return encrypting output stream
|
||||||
|
*/
|
||||||
|
OutputStream encrypt(OutputStream outputStream) throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import sonia.scm.security.KeyGenerator;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXB;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util methods for configuration files.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
final class ConfigFiles {
|
||||||
|
|
||||||
|
private static final KeyGenerator keyGenerator = new SecureRandomKeyGenerator();
|
||||||
|
|
||||||
|
// SCM Config Version 2
|
||||||
|
@VisibleForTesting
|
||||||
|
static final byte[] VERSION_IDENTIFIER = "SCV2".getBytes(Charsets.US_ASCII);
|
||||||
|
|
||||||
|
private ConfigFiles() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the file is encrypted with the v2 format.
|
||||||
|
*
|
||||||
|
* @param file configuration file
|
||||||
|
*
|
||||||
|
* @return {@code true} for format v2
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static boolean isFormatV2(File file) throws IOException {
|
||||||
|
try (InputStream input = new FileInputStream(file)) {
|
||||||
|
byte[] bytes = new byte[VERSION_IDENTIFIER.length];
|
||||||
|
input.read(bytes);
|
||||||
|
return Arrays.equals(VERSION_IDENTIFIER, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt and parse v1 configuration file.
|
||||||
|
*
|
||||||
|
* @param secretKeyStore key store
|
||||||
|
* @param file configuration file
|
||||||
|
*
|
||||||
|
* @return client configuration
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static ScmClientConfig parseV1(SecretKeyStore secretKeyStore, File file) throws IOException {
|
||||||
|
String secretKey = secretKey(secretKeyStore);
|
||||||
|
CipherStreamHandler cipherStreamHandler = new WeakCipherStreamHandler(secretKey);
|
||||||
|
return decrypt(cipherStreamHandler, new FileInputStream(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt and parse v12configuration file.
|
||||||
|
*
|
||||||
|
* @param secretKeyStore key store
|
||||||
|
* @param file configuration file
|
||||||
|
*
|
||||||
|
* @return client configuration
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static ScmClientConfig parseV2(SecretKeyStore secretKeyStore, File file) throws IOException {
|
||||||
|
String secretKey = secretKey(secretKeyStore);
|
||||||
|
CipherStreamHandler cipherStreamHandler = new AesCipherStreamHandler(secretKey);
|
||||||
|
try (InputStream input = new FileInputStream(file)) {
|
||||||
|
input.skip(VERSION_IDENTIFIER.length);
|
||||||
|
return decrypt(cipherStreamHandler, input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store encrypt and write the configuration to the given file.
|
||||||
|
* Note the method uses always the latest available format.
|
||||||
|
*
|
||||||
|
* @param secretKeyStore key store
|
||||||
|
* @param config configuration
|
||||||
|
* @param file configuration file
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static void store(SecretKeyStore secretKeyStore, ScmClientConfig config, File file) throws IOException {
|
||||||
|
String secretKey = keyGenerator.createKey();
|
||||||
|
CipherStreamHandler cipherStreamHandler = new AesCipherStreamHandler(secretKey);
|
||||||
|
try (OutputStream output = new FileOutputStream(file)) {
|
||||||
|
output.write(VERSION_IDENTIFIER);
|
||||||
|
encrypt(cipherStreamHandler, output, config);
|
||||||
|
}
|
||||||
|
secretKeyStore.set(secretKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String secretKey(SecretKeyStore secretKeyStore) {
|
||||||
|
String secretKey = secretKeyStore.get();
|
||||||
|
Preconditions.checkState(!Strings.isNullOrEmpty(secretKey), "no stored secret key found");
|
||||||
|
return secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ScmClientConfig decrypt(CipherStreamHandler cipherStreamHandler, InputStream input) throws IOException {
|
||||||
|
try ( InputStream decryptedInputStream = cipherStreamHandler.decrypt(input) ) {
|
||||||
|
return JAXB.unmarshal(decryptedInputStream, ScmClientConfig.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void encrypt(CipherStreamHandler cipherStreamHandler, OutputStream output, ScmClientConfig clientConfig) throws IOException {
|
||||||
|
try ( OutputStream encryptedOutputStream = cipherStreamHandler.encrypt(output) ) {
|
||||||
|
JAXB.marshal(clientConfig, encryptedOutputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EncryptionSecretKeyStoreWrapper is a wrapper around the {@link SecretKeyStore} interface. The wrapper will
|
||||||
|
* encrypt the passed secret keys, before they are written to the underlying {@link SecretKeyStore} implementation. The
|
||||||
|
* wrapper will also honor old unencrypted keys.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
public class EncryptionSecretKeyStoreWrapper implements SecretKeyStore {
|
||||||
|
|
||||||
|
private static final String ALGORITHM = "AES";
|
||||||
|
|
||||||
|
private static final SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
// i know storing the key directly in the class is far away from a best practice, but this is a chicken egg type
|
||||||
|
// of problem. We need a key to encrypt the stored keys, however encrypting the keys with a static defined key
|
||||||
|
// is better as storing them as plain text.
|
||||||
|
private static final byte[] SECRET_KEY = new byte[]{ 0x50, 0x61, 0x41, 0x67, 0x55, 0x43, 0x48, 0x7a, 0x48, 0x59,
|
||||||
|
0x7a, 0x57, 0x6b, 0x34, 0x54, 0x62
|
||||||
|
};
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String ENCRYPTED_PREFIX = "SKV2:";
|
||||||
|
|
||||||
|
private SecretKeyStore wrappedSecretKeyStore;
|
||||||
|
|
||||||
|
EncryptionSecretKeyStoreWrapper(SecretKeyStore wrappedSecretKeyStore) {
|
||||||
|
this.wrappedSecretKeyStore = wrappedSecretKeyStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(String secretKey) {
|
||||||
|
String encrypted = encrypt(secretKey);
|
||||||
|
wrappedSecretKeyStore.set(ENCRYPTED_PREFIX.concat(encrypted));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encrypt(String value) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = createCipher(Cipher.ENCRYPT_MODE);
|
||||||
|
byte[] raw = cipher.doFinal(value.getBytes(Charsets.UTF_8));
|
||||||
|
return encode(raw);
|
||||||
|
} catch (IllegalBlockSizeException | BadPaddingException ex) {
|
||||||
|
throw new ScmConfigException("failed to encrypt key", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encode(byte[] raw) {
|
||||||
|
return BaseEncoding.base64().encode(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get() {
|
||||||
|
String value = wrappedSecretKeyStore.get();
|
||||||
|
if (Strings.nullToEmpty(value).startsWith(ENCRYPTED_PREFIX)) {
|
||||||
|
String encrypted = value.substring(ENCRYPTED_PREFIX.length());
|
||||||
|
return decrypt(encrypted);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String decrypt(String encoded) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = createCipher(Cipher.DECRYPT_MODE);
|
||||||
|
byte[] raw = decode(encoded);
|
||||||
|
return new String(cipher.doFinal(raw), Charsets.UTF_8);
|
||||||
|
} catch (IllegalBlockSizeException | BadPaddingException ex) {
|
||||||
|
throw new ScmConfigException("failed to decrypt key", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] decode(String encoded) {
|
||||||
|
return BaseEncoding.base64().decode(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher createCipher(int mode) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||||
|
SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY, "AES");
|
||||||
|
cipher.init(mode, secretKeySpec, random);
|
||||||
|
return cipher;
|
||||||
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException ex) {
|
||||||
|
throw new ScmConfigException("failed to create key", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
wrappedSecretKeyStore.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,40 +29,39 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
package sonia.scm.repository.xml;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import sonia.scm.repository.Repository;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.adapters.XmlAdapter;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* SecretKeyStore implementation with uses {@link Preferences}.
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
*/
|
*/
|
||||||
public class XmlRepositoryMapAdapter extends XmlAdapter<XmlRepositoryList, Map<String, Repository>> {
|
public class PrefsSecretKeyStore implements SecretKeyStore {
|
||||||
|
|
||||||
@Override
|
private static final String PREF_SECRET_KEY = "scm.client.key";
|
||||||
public XmlRepositoryList marshal(Map<String, Repository> repositoryMap) {
|
|
||||||
return new XmlRepositoryList(repositoryMap);
|
private final Preferences preferences;
|
||||||
|
|
||||||
|
PrefsSecretKeyStore() {
|
||||||
|
// we use ScmClientConfigFileHandler as base for backward compatibility
|
||||||
|
preferences = Preferences.userNodeForPackage(ScmClientConfigFileHandler.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Repository> unmarshal(XmlRepositoryList repositories) {
|
public void set(String secretKey) {
|
||||||
Map<String, Repository> repositoryMap = new LinkedHashMap<>();
|
preferences.put(PREF_SECRET_KEY, secretKey);
|
||||||
|
|
||||||
for (Repository repository : repositories) {
|
|
||||||
repositoryMap.put(XmlRepositoryDatabase.createKey(repository),
|
|
||||||
repository);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return repositoryMap;
|
@Override
|
||||||
|
public String get() {
|
||||||
|
return preferences.get(PREF_SECRET_KEY, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
preferences.remove(PREF_SECRET_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SecretKeyStore is able to read and write secret keys.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
public interface SecretKeyStore {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the given secret key to the store.
|
||||||
|
*
|
||||||
|
* @param secretKey secret key to write
|
||||||
|
*/
|
||||||
|
void set(String secretKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the secret key from the store. The method returns {@code null} if no secret key was stored.
|
||||||
|
*
|
||||||
|
* @return secret key or {@code null}
|
||||||
|
*/
|
||||||
|
String get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the secret key from store.
|
||||||
|
*/
|
||||||
|
void remove();
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import sonia.scm.security.KeyGenerator;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create keys by using {@link SecureRandom}. The SecureRandomKeyGenerator produces aes compatible keys.
|
||||||
|
* Warning the class is not thread safe.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
public class SecureRandomKeyGenerator implements KeyGenerator {
|
||||||
|
|
||||||
|
private SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
// key length 16 for aes128
|
||||||
|
@VisibleForTesting
|
||||||
|
static final int KEY_LENGTH = 16;
|
||||||
|
|
||||||
|
private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
private static final String LOWER = UPPER.toLowerCase(Locale.ENGLISH);
|
||||||
|
private static final String DIGITS = "0123456789";
|
||||||
|
private static final char[] ALL = (UPPER + LOWER + DIGITS).toCharArray();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createKey() {
|
||||||
|
char[] key = new char[KEY_LENGTH];
|
||||||
|
for (int idx = 0; idx < KEY_LENGTH; ++idx) {
|
||||||
|
key[idx] = ALL[random.nextInt(ALL.length)];
|
||||||
|
}
|
||||||
|
return new String(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import javax.crypto.*;
|
||||||
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
import javax.crypto.spec.PBEParameterSpec;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weak implementation of {@link CipherStreamHandler}. This is the old implementation, which was used in versions prior
|
||||||
|
* 1.60.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*
|
||||||
|
* @see <a href="https://bitbucket.org/sdorra/scm-manager/issues/978/iteration-count-for-password-based">Issue 978</a>
|
||||||
|
* @see <a href="https://bitbucket.org/sdorra/scm-manager/issues/979/constant-salts-for-pbe-are-insecure">Issue 979</a>
|
||||||
|
*/
|
||||||
|
public class WeakCipherStreamHandler implements CipherStreamHandler {
|
||||||
|
|
||||||
|
private static final String SALT = "AE16347F";
|
||||||
|
private static final int SPEC_ITERATION = 12;
|
||||||
|
private static final String CIPHER_NAME = "PBEWithMD5AndDES";
|
||||||
|
|
||||||
|
private final char[] secretKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new handler with the given secret key.
|
||||||
|
*
|
||||||
|
* @param secretKey secret key
|
||||||
|
*/
|
||||||
|
WeakCipherStreamHandler(String secretKey) {
|
||||||
|
this.secretKey = secretKey.toCharArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream decrypt(InputStream inputStream) {
|
||||||
|
try {
|
||||||
|
Cipher c = createCipher(Cipher.DECRYPT_MODE);
|
||||||
|
return new CipherInputStream(inputStream, c);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new ScmConfigException("could not encrypt output stream", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream encrypt(OutputStream outputStream) {
|
||||||
|
try {
|
||||||
|
Cipher c = createCipher(Cipher.ENCRYPT_MODE);
|
||||||
|
return new CipherOutputStream(outputStream, c);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new ScmConfigException("could not encrypt output stream", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher createCipher(int mode)
|
||||||
|
throws NoSuchAlgorithmException, NoSuchPaddingException,
|
||||||
|
InvalidKeySpecException, InvalidKeyException,
|
||||||
|
InvalidAlgorithmParameterException
|
||||||
|
{
|
||||||
|
SecretKey sk = createSecretKey();
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
|
||||||
|
PBEParameterSpec spec = new PBEParameterSpec(SALT.getBytes(), SPEC_ITERATION);
|
||||||
|
|
||||||
|
cipher.init(mode, sk, spec);
|
||||||
|
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey createSecretKey()
|
||||||
|
throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||||
|
{
|
||||||
|
PBEKeySpec keySpec = new PBEKeySpec(secretKey);
|
||||||
|
SecretKeyFactory factory = SecretKeyFactory.getInstance(CIPHER_NAME);
|
||||||
|
|
||||||
|
return factory.generateSecret(keySpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,70 +30,39 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
package sonia.scm.repository;
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.security.KeyGenerator;
|
||||||
|
|
||||||
/**
|
import java.io.*;
|
||||||
* Type of permissionPrefix for a {@link Repository}.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
public enum PermissionType
|
|
||||||
{
|
|
||||||
|
|
||||||
/** read permision */
|
import static org.junit.Assert.assertEquals;
|
||||||
READ(0, "repository:read,pull:"),
|
|
||||||
|
|
||||||
/** read and write permissionPrefix */
|
public class AesCipherStreamHandlerTest {
|
||||||
WRITE(10, "repository:read,pull,push:"),
|
|
||||||
|
|
||||||
/**
|
private final KeyGenerator keyGenerator = new SecureRandomKeyGenerator();
|
||||||
* read, write and
|
|
||||||
* also the ability to manage the properties and permissions
|
|
||||||
*/
|
|
||||||
OWNER(100, "repository:*:");
|
|
||||||
|
|
||||||
/**
|
@Test
|
||||||
* Constructs a new permissionPrefix type
|
public void testEncryptAndDecrypt() throws IOException {
|
||||||
*
|
AesCipherStreamHandler cipherStreamHandler = new AesCipherStreamHandler(keyGenerator.createKey());
|
||||||
*
|
|
||||||
* @param value
|
// douglas adams
|
||||||
*/
|
String content = "If you try and take a cat apart to see how it works, the first thing you have on your hands is a nonworking cat.";
|
||||||
private PermissionType(int value, String permissionPrefix)
|
|
||||||
{
|
// encrypt
|
||||||
this.value = value;
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
this.permissionPrefix = permissionPrefix;
|
OutputStream encryptedOutput = cipherStreamHandler.encrypt(output);
|
||||||
|
encryptedOutput.write(content.getBytes(Charsets.UTF_8));
|
||||||
|
encryptedOutput.close();
|
||||||
|
|
||||||
|
InputStream input = new ByteArrayInputStream(output.toByteArray());
|
||||||
|
input = cipherStreamHandler.decrypt(input);
|
||||||
|
byte[] decrypted = ByteStreams.toByteArray(input);
|
||||||
|
|
||||||
|
assertEquals(content, new String(decrypted, Charsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @since 2.0.0
|
|
||||||
*/
|
|
||||||
public String getPermissionPrefix()
|
|
||||||
{
|
|
||||||
return permissionPrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer representation of the {@link PermissionType}
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return integer representation
|
|
||||||
*/
|
|
||||||
public int getValue()
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final String permissionPrefix;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final int value;
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXB;
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
final class ClientConfigurationTests {
|
||||||
|
|
||||||
|
private ClientConfigurationTests() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testCipherStream(CipherStreamHandler cipherStreamHandler, String content) throws IOException {
|
||||||
|
byte[] encrypted = encrypt(cipherStreamHandler, content);
|
||||||
|
String decrypted = decrypt(cipherStreamHandler, encrypted);
|
||||||
|
assertEquals(content, decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static byte[] encrypt(CipherStreamHandler cipherStreamHandler, String content) throws IOException {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
OutputStream encryptedOutput = cipherStreamHandler.encrypt(output);
|
||||||
|
encryptedOutput.write(content.getBytes(Charsets.UTF_8));
|
||||||
|
encryptedOutput.close();
|
||||||
|
return output.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String decrypt(CipherStreamHandler cipherStreamHandler, byte[] encrypted) throws IOException {
|
||||||
|
InputStream input = new ByteArrayInputStream(encrypted);
|
||||||
|
input = cipherStreamHandler.decrypt(input);
|
||||||
|
byte[] decrypted = ByteStreams.toByteArray(input);
|
||||||
|
input.close();
|
||||||
|
|
||||||
|
return new String(decrypted, Charsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertSampleConfig(ScmClientConfig config) {
|
||||||
|
ServerConfig defaultConfig;
|
||||||
|
defaultConfig = config.getDefaultConfig();
|
||||||
|
|
||||||
|
assertEquals("http://localhost:8080/scm", defaultConfig.getServerUrl());
|
||||||
|
assertEquals("admin", defaultConfig.getUsername());
|
||||||
|
assertEquals("admin123", defaultConfig.getPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
static ScmClientConfig createSampleConfig() {
|
||||||
|
ScmClientConfig config = new ScmClientConfig();
|
||||||
|
ServerConfig defaultConfig = config.getDefaultConfig();
|
||||||
|
defaultConfig.setServerUrl("http://localhost:8080/scm");
|
||||||
|
defaultConfig.setUsername("admin");
|
||||||
|
defaultConfig.setPassword("admin123");
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encrypt(CipherStreamHandler cipherStreamHandler, ScmClientConfig config, File file) throws IOException {
|
||||||
|
try (OutputStream output = cipherStreamHandler.encrypt(new FileOutputStream(file))) {
|
||||||
|
JAXB.marshal(config, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class ConfigFilesTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsFormatV2() throws IOException {
|
||||||
|
byte[] content = "The door was the way to... to... The Door was The Way".getBytes(Charsets.UTF_8);
|
||||||
|
|
||||||
|
File fileV1 = temporaryFolder.newFile();
|
||||||
|
Files.write(content, fileV1);
|
||||||
|
|
||||||
|
assertFalse(ConfigFiles.isFormatV2(fileV1));
|
||||||
|
|
||||||
|
File fileV2 = temporaryFolder.newFile();
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
baos.write(ConfigFiles.VERSION_IDENTIFIER);
|
||||||
|
baos.write(content);
|
||||||
|
Files.write(baos.toByteArray(), fileV2);
|
||||||
|
|
||||||
|
assertTrue(ConfigFiles.isFormatV2(fileV2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseV1() throws IOException {
|
||||||
|
InMemorySecretKeyStore keyStore = createKeyStore();
|
||||||
|
WeakCipherStreamHandler handler = new WeakCipherStreamHandler(keyStore.get());
|
||||||
|
|
||||||
|
ScmClientConfig config = ClientConfigurationTests.createSampleConfig();
|
||||||
|
File file = temporaryFolder.newFile();
|
||||||
|
ClientConfigurationTests.encrypt(handler, config, file);
|
||||||
|
|
||||||
|
config = ConfigFiles.parseV1(keyStore, file);
|
||||||
|
ClientConfigurationTests.assertSampleConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void storeAndParseV2() throws IOException {
|
||||||
|
InMemorySecretKeyStore keyStore = new InMemorySecretKeyStore();
|
||||||
|
ScmClientConfig config = ClientConfigurationTests.createSampleConfig();
|
||||||
|
File file = temporaryFolder.newFile();
|
||||||
|
|
||||||
|
ConfigFiles.store(keyStore, config, file);
|
||||||
|
|
||||||
|
String key = keyStore.get();
|
||||||
|
assertNotNull(key);
|
||||||
|
|
||||||
|
config = ConfigFiles.parseV2(keyStore, file);
|
||||||
|
ClientConfigurationTests.assertSampleConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InMemorySecretKeyStore createKeyStore() {
|
||||||
|
String secretKey = new SecureRandomKeyGenerator().createKey();
|
||||||
|
InMemorySecretKeyStore keyStore = new InMemorySecretKeyStore();
|
||||||
|
keyStore.set(secretKey);
|
||||||
|
return keyStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class EncryptionSecretKeyStoreWrapperTest {
|
||||||
|
|
||||||
|
private SecretKeyStore secretKeyStore = new InMemorySecretKeyStore();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionKeyStoreWrapper() {
|
||||||
|
EncryptionSecretKeyStoreWrapper wrapper = new EncryptionSecretKeyStoreWrapper(secretKeyStore);
|
||||||
|
wrapper.set("mysecretkey");
|
||||||
|
|
||||||
|
assertEquals("mysecretkey", wrapper.get());
|
||||||
|
assertTrue(secretKeyStore.get().startsWith(EncryptionSecretKeyStoreWrapper.ENCRYPTED_PREFIX));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionKeyStoreWrapperWithOldUnencryptedKey() {
|
||||||
|
secretKeyStore.set("mysecretkey");
|
||||||
|
EncryptionSecretKeyStoreWrapper wrapper = new EncryptionSecretKeyStoreWrapper(secretKeyStore);
|
||||||
|
assertEquals("mysecretkey", wrapper.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2014, Sebastian Sdorra
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
@@ -28,22 +28,26 @@
|
|||||||
* http://bitbucket.org/sdorra/scm-manager
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package sonia.scm.repository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constants for Git.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
* @since 1.50
|
|
||||||
*/
|
|
||||||
public final class GitConstants {
|
|
||||||
|
|
||||||
/**
|
package sonia.scm.cli.config;
|
||||||
* Default branch repository property.
|
|
||||||
*/
|
|
||||||
public static final String PROPERTY_DEFAULT_BRANCH = "git.default-branch";
|
|
||||||
|
|
||||||
private GitConstants() {
|
public class InMemorySecretKeyStore implements SecretKeyStore {
|
||||||
|
|
||||||
|
private String secretKey;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(String secretKey) {
|
||||||
|
this.secretKey = secretKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get() {
|
||||||
|
return secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
this.secretKey = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import com.google.common.io.Resources;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import sonia.scm.security.UUIDKeyGenerator;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class ScmClientConfigFileHandlerTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientConfigFileHandler() throws IOException {
|
||||||
|
File configFile = temporaryFolder.newFile();
|
||||||
|
|
||||||
|
ScmClientConfigFileHandler handler = new ScmClientConfigFileHandler(
|
||||||
|
new EncryptionSecretKeyStoreWrapper(new InMemorySecretKeyStore()), configFile
|
||||||
|
);
|
||||||
|
|
||||||
|
ScmClientConfig config = new ScmClientConfig();
|
||||||
|
ServerConfig defaultConfig = config.getDefaultConfig();
|
||||||
|
defaultConfig.setServerUrl("http://localhost:8080/scm");
|
||||||
|
defaultConfig.setUsername("scmadmin");
|
||||||
|
defaultConfig.setPassword("admin123");
|
||||||
|
handler.write(config);
|
||||||
|
|
||||||
|
assertTrue(configFile.exists());
|
||||||
|
|
||||||
|
config = handler.read();
|
||||||
|
defaultConfig = config.getDefaultConfig();
|
||||||
|
assertEquals("http://localhost:8080/scm", defaultConfig.getServerUrl());
|
||||||
|
assertEquals("scmadmin", defaultConfig.getUsername());
|
||||||
|
assertEquals("admin123", defaultConfig.getPassword());
|
||||||
|
|
||||||
|
handler.delete();
|
||||||
|
|
||||||
|
assertFalse(configFile.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientConfigFileHandlerWithOldConfiguration() throws IOException {
|
||||||
|
File configFile = temporaryFolder.newFile();
|
||||||
|
|
||||||
|
// old implementation has used uuids as keys
|
||||||
|
String key = new UUIDKeyGenerator().createKey();
|
||||||
|
|
||||||
|
WeakCipherStreamHandler weakCipherStreamHandler = new WeakCipherStreamHandler(key);
|
||||||
|
ScmClientConfig clientConfig = ClientConfigurationTests.createSampleConfig();
|
||||||
|
ClientConfigurationTests.encrypt(weakCipherStreamHandler, clientConfig, configFile);
|
||||||
|
|
||||||
|
assertFalse(ConfigFiles.isFormatV2(configFile));
|
||||||
|
|
||||||
|
SecretKeyStore secretKeyStore = new EncryptionSecretKeyStoreWrapper(new InMemorySecretKeyStore());
|
||||||
|
secretKeyStore.set(key);
|
||||||
|
|
||||||
|
ScmClientConfigFileHandler handler = new ScmClientConfigFileHandler(
|
||||||
|
secretKeyStore, configFile
|
||||||
|
);
|
||||||
|
|
||||||
|
ScmClientConfig config = handler.read();
|
||||||
|
ClientConfigurationTests.assertSampleConfig(config);
|
||||||
|
|
||||||
|
// ensure key has changed
|
||||||
|
assertNotEquals(key, secretKeyStore.get());
|
||||||
|
|
||||||
|
// ensure config rewritten with v2
|
||||||
|
assertTrue(ConfigFiles.isFormatV2(configFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientConfigFileHandlerWithRealMigration() throws IOException {
|
||||||
|
URL resource = Resources.getResource("sonia/scm/cli/config/scm-cli-config.enc.xml");
|
||||||
|
byte[] bytes = Resources.toByteArray(resource);
|
||||||
|
|
||||||
|
File configFile = temporaryFolder.newFile();
|
||||||
|
Files.write(bytes, configFile);
|
||||||
|
|
||||||
|
String key = "358e018a-0c3c-4339-8266-3874e597305f";
|
||||||
|
SecretKeyStore secretKeyStore = new EncryptionSecretKeyStoreWrapper(new InMemorySecretKeyStore());
|
||||||
|
secretKeyStore.set(key);
|
||||||
|
|
||||||
|
ScmClientConfigFileHandler handler = new ScmClientConfigFileHandler(
|
||||||
|
secretKeyStore, configFile
|
||||||
|
);
|
||||||
|
|
||||||
|
ScmClientConfig config = handler.read();
|
||||||
|
ServerConfig defaultConfig = config.getDefaultConfig();
|
||||||
|
assertEquals("http://hitchhicker.com/scm", defaultConfig.getServerUrl());
|
||||||
|
assertEquals("tricia", defaultConfig.getUsername());
|
||||||
|
assertEquals("trillian123", defaultConfig.getPassword());
|
||||||
|
|
||||||
|
// ensure key has changed
|
||||||
|
assertNotEquals(key, secretKeyStore.get());
|
||||||
|
|
||||||
|
// ensure config rewritten with v2
|
||||||
|
assertTrue(ConfigFiles.isFormatV2(configFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. 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.
|
||||||
|
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class SecureRandomKeyGeneratorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateKey() {
|
||||||
|
SecureRandomKeyGenerator keyGenerator = new SecureRandomKeyGenerator();
|
||||||
|
assertNotNull(keyGenerator.createKey());
|
||||||
|
assertEquals(SecureRandomKeyGenerator.KEY_LENGTH, keyGenerator.createKey().length());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -47,6 +47,7 @@
|
|||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<version>${slf4j.version}</version>
|
<version>${slf4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<artifactId>jcl-over-slf4j</artifactId>
|
<artifactId>jcl-over-slf4j</artifactId>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
@@ -92,6 +93,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.ws.rs</groupId>
|
<groupId>javax.ws.rs</groupId>
|
||||||
<artifactId>javax.ws.rs-api</artifactId>
|
<artifactId>javax.ws.rs-api</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -143,6 +145,28 @@
|
|||||||
<version>${legman.version}</version>
|
<version>${legman.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- xml -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-impl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.activation</groupId>
|
||||||
|
<artifactId>activation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- util -->
|
<!-- util -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -193,6 +217,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<useStandardDocletOptions>true</useStandardDocletOptions>
|
<useStandardDocletOptions>true</useStandardDocletOptions>
|
||||||
<charset>${project.build.sourceEncoding}</charset>
|
<charset>${project.build.sourceEncoding}</charset>
|
||||||
@@ -211,11 +236,24 @@
|
|||||||
<links>
|
<links>
|
||||||
<link>http://download.oracle.com/javase/6/docs/api/</link>
|
<link>http://download.oracle.com/javase/6/docs/api/</link>
|
||||||
<link>http://download.oracle.com/docs/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/</link>
|
<link>http://download.oracle.com/docs/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/</link>
|
||||||
<link>http://jersey.java.net/nonav/apidocs/${jersey.version}/jersey/</link>
|
|
||||||
<link>https://google.github.io/guice/api-docs/${guice.version}/javadoc</link>
|
<link>https://google.github.io/guice/api-docs/${guice.version}/javadoc</link>
|
||||||
<link>http://www.slf4j.org/api/</link>
|
<link>http://www.slf4j.org/api/</link>
|
||||||
<link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link>
|
<link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link>
|
||||||
</links>
|
</links>
|
||||||
|
<doclet>org.jboss.apiviz.APIviz</doclet>
|
||||||
|
<docletArtifact>
|
||||||
|
<groupId>org.jboss.apiviz</groupId>
|
||||||
|
<artifactId>apiviz</artifactId>
|
||||||
|
<version>1.3.2.GA</version>
|
||||||
|
</docletArtifact>
|
||||||
|
<additionalOptions>
|
||||||
|
<additionalOption>
|
||||||
|
-sourceclasspath ${project.build.outputDirectory}
|
||||||
|
</additionalOption>
|
||||||
|
<additionalOption>
|
||||||
|
-nopackagediagram
|
||||||
|
</additionalOption>
|
||||||
|
</additionalOptions>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package sonia.scm;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class BadRequestException extends ExceptionWithContext {
|
||||||
|
public BadRequestException(List<ContextEntry> context, String message) {
|
||||||
|
super(context, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ package sonia.scm;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
@@ -43,6 +44,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
@@ -105,8 +107,26 @@ public class BasicContextProvider implements SCMContextProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
BasicContextProvider(File baseDirectory, String version, Stage stage) {
|
||||||
|
this.baseDirectory = baseDirectory;
|
||||||
|
this.version = version;
|
||||||
|
this.stage = stage;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Path resolve(Path path) {
|
||||||
|
if (path.isAbsolute()) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseDirectory.toPath().resolve(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import static java.util.Collections.unmodifiableList;
|
|||||||
|
|
||||||
public abstract class ExceptionWithContext extends RuntimeException {
|
public abstract class ExceptionWithContext extends RuntimeException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 4327413456580409224L;
|
||||||
|
|
||||||
private final List<ContextEntry> context;
|
private final List<ContextEntry> context;
|
||||||
|
|
||||||
public ExceptionWithContext(List<ContextEntry> context, String message) {
|
public ExceptionWithContext(List<ContextEntry> context, String message) {
|
||||||
|
|||||||
@@ -40,13 +40,14 @@ import java.util.Collections;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @version 1.6
|
* @version 1.6
|
||||||
*/
|
*/
|
||||||
public class NotSupportedFeatureException extends ExceptionWithContext {
|
@SuppressWarnings("squid:MaximumInheritanceDepth") // exceptions have a deep inheritance depth themselves; therefore we accept this here
|
||||||
|
public class FeatureNotSupportedException extends BadRequestException {
|
||||||
|
|
||||||
private static final long serialVersionUID = 256498734456613496L;
|
private static final long serialVersionUID = 256498734456613496L;
|
||||||
|
|
||||||
private static final String CODE = "9SR8G0kmU1";
|
private static final String CODE = "9SR8G0kmU1";
|
||||||
|
|
||||||
public NotSupportedFeatureException(String feature)
|
public FeatureNotSupportedException(String feature)
|
||||||
{
|
{
|
||||||
super(Collections.emptyList(),createMessage(feature));
|
super(Collections.emptyList(),createMessage(feature));
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,6 @@ package sonia.scm;
|
|||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base class of all handlers.
|
* The base class of all handlers.
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import static java.util.stream.Collectors.joining;
|
|||||||
|
|
||||||
public class NotFoundException extends ExceptionWithContext {
|
public class NotFoundException extends ExceptionWithContext {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1710455380886499111L;
|
||||||
|
|
||||||
private static final String CODE = "AGR7UzkhA1";
|
private static final String CODE = "AGR7UzkhA1";
|
||||||
|
|
||||||
public NotFoundException(Class type, String id) {
|
public NotFoundException(Class type, String id) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ package sonia.scm;
|
|||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main class for retrieving the home and the version of the SCM-Manager.
|
* The main class for retrieving the home and the version of the SCM-Manager.
|
||||||
@@ -65,6 +66,17 @@ public interface SCMContextProvider extends Closeable
|
|||||||
*/
|
*/
|
||||||
public File getBaseDirectory();
|
public File getBaseDirectory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the given path against the base directory.
|
||||||
|
*
|
||||||
|
* @param path path to resolve
|
||||||
|
*
|
||||||
|
* @return absolute resolved path
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
Path resolve(Path path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current stage of SCM-Manager.
|
* Returns the current stage of SCM-Manager.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package sonia.scm;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static java.util.Collections.unmodifiableCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this exception to handle invalid input values that cannot be handled using
|
||||||
|
* <a href="https://docs.oracle.com/javaee/7/tutorial/bean-validation001.htm#GIRCZ">JEE bean validation</a>.
|
||||||
|
* Use the {@link Builder} to conditionally create a new exception:
|
||||||
|
* <pre>
|
||||||
|
* Builder
|
||||||
|
* .doThrow()
|
||||||
|
* .violation("name or alias must not be empty if not anonymous", "myParameter", "name")
|
||||||
|
* .violation("name or alias must not be empty if not anonymous", "myParameter", "alias")
|
||||||
|
* .when(myParameter.getName() == null && myParameter.getAlias() == null && !myParameter.isAnonymous())
|
||||||
|
* .andThrow()
|
||||||
|
* .violation("name must be empty if anonymous", "myParameter", "name")
|
||||||
|
* .when(myParameter.getName() != null && myParameter.isAnonymous());
|
||||||
|
* </pre>
|
||||||
|
* Mind that using this way you do not have to use if-else constructs.
|
||||||
|
*/
|
||||||
|
public class ScmConstraintViolationException extends RuntimeException implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 6904534307450229887L;
|
||||||
|
|
||||||
|
private final Collection<ScmConstraintViolation> violations;
|
||||||
|
|
||||||
|
private final String furtherInformation;
|
||||||
|
|
||||||
|
private ScmConstraintViolationException(Collection<ScmConstraintViolation> violations, String furtherInformation) {
|
||||||
|
this.violations = violations;
|
||||||
|
this.furtherInformation = furtherInformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The violations that caused this exception.
|
||||||
|
*/
|
||||||
|
public Collection<ScmConstraintViolation> getViolations() {
|
||||||
|
return unmodifiableCollection(violations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional URL for more informations about this constraint violation.
|
||||||
|
*/
|
||||||
|
public String getUrl() {
|
||||||
|
return furtherInformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder to conditionally create constraint violations.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private final Collection<ScmConstraintViolation> violations = new ArrayList<>();
|
||||||
|
private String furtherInformation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to create a new builder instance.
|
||||||
|
*/
|
||||||
|
public static Builder doThrow() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets this builder to check for further violations.
|
||||||
|
* @return this builder instance.
|
||||||
|
*/
|
||||||
|
public Builder andThrow() {
|
||||||
|
this.violations.clear();
|
||||||
|
this.furtherInformation = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the violation with a custom message and the affected property. When more than one property is affected,
|
||||||
|
* you can call this method multiple times.
|
||||||
|
* @param message The message describing the violation.
|
||||||
|
* @param pathElements The affected property denoted by the path to reach this property,
|
||||||
|
* eg. "someParameter", "complexProperty", "attribute"
|
||||||
|
* @return this builder instance.
|
||||||
|
*/
|
||||||
|
public Builder violation(String message, String... pathElements) {
|
||||||
|
this.violations.add(new ScmConstraintViolation(message, pathElements));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to specify a URL with further information about this violation and hints how to solve this.
|
||||||
|
* This is optional.
|
||||||
|
* @return this builder instance.
|
||||||
|
*/
|
||||||
|
public Builder withFurtherInformation(String furtherInformation) {
|
||||||
|
this.furtherInformation = furtherInformation;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the given condition is <code>true</code>, a exception will be thrown. Otherwise this simply resets this
|
||||||
|
* builder and does nothing else.
|
||||||
|
* @param condition The condition that indicates a violation of this constraint.
|
||||||
|
* @return this builder instance.
|
||||||
|
*/
|
||||||
|
public Builder when(boolean condition) {
|
||||||
|
if (condition && !this.violations.isEmpty()) {
|
||||||
|
throw new ScmConstraintViolationException(violations, furtherInformation);
|
||||||
|
}
|
||||||
|
return andThrow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single constraint violation.
|
||||||
|
*/
|
||||||
|
public static class ScmConstraintViolation implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -6900317468157084538L;
|
||||||
|
|
||||||
|
private final String message;
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
private ScmConstraintViolation(String message, String... pathElements) {
|
||||||
|
this.message = message;
|
||||||
|
this.path = String.join(".", pathElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPropertyPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,15 +77,13 @@ public final class ScmState
|
|||||||
* @param repositoryTypes available repository types
|
* @param repositoryTypes available repository types
|
||||||
* @param defaultUserType default user type
|
* @param defaultUserType default user type
|
||||||
* @param clientConfig client configuration
|
* @param clientConfig client configuration
|
||||||
* @param assignedPermission assigned permissions
|
|
||||||
* @param availablePermissions list of available permissions
|
* @param availablePermissions list of available permissions
|
||||||
*
|
*
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public ScmState(String version, User user, Collection<String> groups,
|
public ScmState(String version, User user, Collection<String> groups,
|
||||||
String token, Collection<RepositoryType> repositoryTypes, String defaultUserType,
|
String token, Collection<RepositoryType> repositoryTypes, String defaultUserType,
|
||||||
ScmClientConfig clientConfig, List<String> assignedPermission,
|
ScmClientConfig clientConfig, Collection<PermissionDescriptor> availablePermissions)
|
||||||
List<PermissionDescriptor> availablePermissions)
|
|
||||||
{
|
{
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
@@ -94,24 +92,11 @@ public final class ScmState
|
|||||||
this.repositoryTypes = repositoryTypes;
|
this.repositoryTypes = repositoryTypes;
|
||||||
this.clientConfig = clientConfig;
|
this.clientConfig = clientConfig;
|
||||||
this.defaultUserType = defaultUserType;
|
this.defaultUserType = defaultUserType;
|
||||||
this.assignedPermissions = assignedPermission;
|
|
||||||
this.availablePermissions = availablePermissions;
|
this.availablePermissions = availablePermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list of assigned permissions.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return list of assigned permissions
|
|
||||||
* @since 1.31
|
|
||||||
*/
|
|
||||||
public List<String> getAssignedPermissions()
|
|
||||||
{
|
|
||||||
return assignedPermissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of available global permissions.
|
* Returns a list of available global permissions.
|
||||||
*
|
*
|
||||||
@@ -119,7 +104,7 @@ public final class ScmState
|
|||||||
* @return available global permissions
|
* @return available global permissions
|
||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
public List<PermissionDescriptor> getAvailablePermissions()
|
public Collection<PermissionDescriptor> getAvailablePermissions()
|
||||||
{
|
{
|
||||||
return availablePermissions;
|
return availablePermissions;
|
||||||
}
|
}
|
||||||
@@ -225,14 +210,11 @@ public final class ScmState
|
|||||||
/** authentication token */
|
/** authentication token */
|
||||||
private String token;
|
private String token;
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private List<String> assignedPermissions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Avaliable global permission
|
* Avaliable global permission
|
||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
private List<PermissionDescriptor> availablePermissions;
|
private Collection<PermissionDescriptor> availablePermissions;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private ScmClientConfig clientConfig;
|
private ScmClientConfig clientConfig;
|
||||||
|
|||||||
@@ -74,20 +74,17 @@ public final class ScmStateFactory
|
|||||||
* @param repositoryManger repository manager
|
* @param repositoryManger repository manager
|
||||||
* @param userManager user manager
|
* @param userManager user manager
|
||||||
* @param securitySystem security system
|
* @param securitySystem security system
|
||||||
* @param authorizationCollector authorization collector
|
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public ScmStateFactory(SCMContextProvider contextProvider,
|
public ScmStateFactory(SCMContextProvider contextProvider,
|
||||||
ScmConfiguration configuration, RepositoryManager repositoryManger,
|
ScmConfiguration configuration, RepositoryManager repositoryManger,
|
||||||
UserManager userManager, SecuritySystem securitySystem,
|
UserManager userManager, SecuritySystem securitySystem)
|
||||||
AuthorizationCollector authorizationCollector)
|
|
||||||
{
|
{
|
||||||
this.contextProvider = contextProvider;
|
this.contextProvider = contextProvider;
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.repositoryManger = repositoryManger;
|
this.repositoryManger = repositoryManger;
|
||||||
this.userManager = userManager;
|
this.userManager = userManager;
|
||||||
this.securitySystem = securitySystem;
|
this.securitySystem = securitySystem;
|
||||||
this.authorizationCollector = authorizationCollector;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -101,8 +98,7 @@ public final class ScmStateFactory
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public ScmState createAnonymousState()
|
public ScmState createAnonymousState()
|
||||||
{
|
{
|
||||||
return createState(SCMContext.ANONYMOUS, Collections.EMPTY_LIST, null,
|
return createState(SCMContext.ANONYMOUS, Collections.EMPTY_LIST, null, Collections.EMPTY_LIST);
|
||||||
Collections.EMPTY_LIST, Collections.EMPTY_LIST);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,23 +130,19 @@ public final class ScmStateFactory
|
|||||||
User user = collection.oneByType(User.class);
|
User user = collection.oneByType(User.class);
|
||||||
GroupNames groups = collection.oneByType(GroupNames.class);
|
GroupNames groups = collection.oneByType(GroupNames.class);
|
||||||
|
|
||||||
List<PermissionDescriptor> ap = Collections.EMPTY_LIST;
|
Collection<PermissionDescriptor> ap = Collections.EMPTY_LIST;
|
||||||
|
|
||||||
if (subject.hasRole(Role.ADMIN))
|
if (subject.hasRole(Role.ADMIN))
|
||||||
{
|
{
|
||||||
ap = securitySystem.getAvailablePermissions();
|
ap = securitySystem.getAvailablePermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> permissions =
|
return createState(user, groups.getCollection(), token, ap);
|
||||||
ImmutableList.copyOf(
|
|
||||||
authorizationCollector.collect().getStringPermissions());
|
|
||||||
|
|
||||||
return createState(user, groups.getCollection(), token, permissions, ap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScmState createState(User user, Collection<String> groups,
|
private ScmState createState(User user, Collection<String> groups,
|
||||||
String token, List<String> assignedPermissions,
|
String token,
|
||||||
List<PermissionDescriptor> availablePermissions)
|
Collection<PermissionDescriptor> availablePermissions)
|
||||||
{
|
{
|
||||||
User u = user.clone();
|
User u = user.clone();
|
||||||
|
|
||||||
@@ -159,15 +151,11 @@ public final class ScmStateFactory
|
|||||||
|
|
||||||
return new ScmState(contextProvider.getVersion(), u, groups, token,
|
return new ScmState(contextProvider.getVersion(), u, groups, token,
|
||||||
repositoryManger.getConfiguredTypes(), userManager.getDefaultType(),
|
repositoryManger.getConfiguredTypes(), userManager.getDefaultType(),
|
||||||
new ScmClientConfig(configuration), assignedPermissions,
|
new ScmClientConfig(configuration), availablePermissions);
|
||||||
availablePermissions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** authorization collector */
|
|
||||||
private final AuthorizationCollector authorizationCollector;
|
|
||||||
|
|
||||||
/** configuration */
|
/** configuration */
|
||||||
private final ScmConfiguration configuration;
|
private final ScmConfiguration configuration;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
|
|||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
import org.mapstruct.Mapping;
|
import org.mapstruct.Mapping;
|
||||||
|
|
||||||
public abstract class BaseMapper<T, D extends HalRepresentation> implements InstantAttributeMapper {
|
public abstract class BaseMapper<T, D extends HalRepresentation> extends HalAppenderMapper implements InstantAttributeMapper {
|
||||||
|
|
||||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||||
public abstract D map(T modelObject);
|
public abstract D map(T modelObject);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.Embedded;
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -7,7 +8,6 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@@ -34,16 +34,7 @@ public class ChangesetDto extends HalRepresentation {
|
|||||||
*/
|
*/
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
@Override
|
public ChangesetDto(Links links, Embedded embedded) {
|
||||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
super(links, embedded);
|
||||||
protected HalRepresentation add(Links links) {
|
|
||||||
return super.add(links);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
|
||||||
protected HalRepresentation withEmbedded(String rel, List<? extends HalRepresentation> halRepresentations) {
|
|
||||||
return super.withEmbedded(rel, halRepresentations);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import org.mapstruct.Context;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import sonia.scm.repository.Changeset;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
public interface ChangesetToChangesetDtoMapper {
|
||||||
|
|
||||||
|
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||||
|
ChangesetDto map(Changeset changeset, @Context Repository repository);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link HalAppender} can be used within an {@link HalEnricher} to append hateoas links to a json response.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public interface HalAppender {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends one link to the json response.
|
||||||
|
*
|
||||||
|
* @param rel name of relation
|
||||||
|
* @param href link uri
|
||||||
|
*/
|
||||||
|
void appendLink(String rel, String href);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a builder which is able to append an array of links to the resource.
|
||||||
|
*
|
||||||
|
* @param rel name of link relation
|
||||||
|
* @return multi link builder
|
||||||
|
*/
|
||||||
|
LinkArrayBuilder linkArrayBuilder(String rel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends one embedded object to the json response.
|
||||||
|
*
|
||||||
|
* @param rel name of relation
|
||||||
|
* @param embeddedItem embedded object
|
||||||
|
*/
|
||||||
|
void appendEmbedded(String rel, HalRepresentation embeddedItem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for link arrays.
|
||||||
|
*/
|
||||||
|
interface LinkArrayBuilder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a link to the array.
|
||||||
|
*
|
||||||
|
* @param name name of link
|
||||||
|
* @param href link target
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
LinkArrayBuilder append(String name, String href);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the array and appends the it to the json response.
|
||||||
|
*/
|
||||||
|
void build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
public class HalAppenderMapper {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private HalEnricherRegistry registry;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void setRegistry(HalEnricherRegistry registry) {
|
||||||
|
this.registry = registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyEnrichers(HalAppender appender, Object source, Object... contextEntries) {
|
||||||
|
// null check is only their to not break existing tests
|
||||||
|
if (registry != null) {
|
||||||
|
|
||||||
|
Object[] ctx = new Object[contextEntries.length + 1];
|
||||||
|
ctx[0] = source;
|
||||||
|
for (int i = 0; i < contextEntries.length; i++) {
|
||||||
|
ctx[i + 1] = contextEntries[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
HalEnricherContext context = HalEnricherContext.of(ctx);
|
||||||
|
|
||||||
|
Iterable<HalEnricher> enrichers = registry.allByType(source.getClass());
|
||||||
|
for (HalEnricher enricher : enrichers) {
|
||||||
|
enricher.enrich(context, appender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import sonia.scm.plugin.ExtensionPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link HalEnricher} can be used to append hal specific attributes, such as links, to the json response.
|
||||||
|
* To register an enricher use the {@link Enrich} annotation or the {@link HalEnricherRegistry} which is available
|
||||||
|
* via injection.
|
||||||
|
*
|
||||||
|
* <b>Warning:</b> enrichers are always registered as singletons.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@ExtensionPoint
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface HalEnricher {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enriches the response with hal specific attributes.
|
||||||
|
*
|
||||||
|
* @param context contains the source for the json mapping and related objects
|
||||||
|
* @param appender can be used to append links or embedded objects to the json response
|
||||||
|
*/
|
||||||
|
void enrich(HalEnricherContext context, HalAppender appender);
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context object for the {@link HalEnricher}. The context holds the source object for the json and all related
|
||||||
|
* objects, which can be useful for the enrichment.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public final class HalEnricherContext {
|
||||||
|
|
||||||
|
private final Map<Class, Object> instanceMap;
|
||||||
|
|
||||||
|
private HalEnricherContext(Map<Class,Object> instanceMap) {
|
||||||
|
this.instanceMap = instanceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a context with the given entries
|
||||||
|
*
|
||||||
|
* @param instances entries of the context
|
||||||
|
*
|
||||||
|
* @return context of given entries
|
||||||
|
*/
|
||||||
|
public static HalEnricherContext of(Object... instances) {
|
||||||
|
ImmutableMap.Builder<Class, Object> builder = ImmutableMap.builder();
|
||||||
|
for (Object instance : instances) {
|
||||||
|
builder.put(instance.getClass(), instance);
|
||||||
|
}
|
||||||
|
return new HalEnricherContext(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the registered object from the context. The method will return an empty optional, if no object with the
|
||||||
|
* given type was registered.
|
||||||
|
*
|
||||||
|
* @param type type of instance
|
||||||
|
* @param <T> type of instance
|
||||||
|
* @return optional instance
|
||||||
|
*/
|
||||||
|
public <T> Optional<T> oneByType(Class<T> type) {
|
||||||
|
Object instance = instanceMap.get(type);
|
||||||
|
if (instance != null) {
|
||||||
|
return Optional.of(type.cast(instance));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the registered object from the context, but throws an {@link NoSuchElementException} if the type was not
|
||||||
|
* registered.
|
||||||
|
*
|
||||||
|
* @param type type of instance
|
||||||
|
* @param <T> type of instance
|
||||||
|
* @return instance
|
||||||
|
*/
|
||||||
|
public <T> T oneRequireByType(Class<T> type) {
|
||||||
|
Optional<T> instance = oneByType(type);
|
||||||
|
if (instance.isPresent()) {
|
||||||
|
return instance.get();
|
||||||
|
} else {
|
||||||
|
throw new NoSuchElementException("No instance for given type present");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.common.collect.HashMultimap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
import sonia.scm.plugin.Extension;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link HalEnricherRegistry} is responsible for binding {@link HalEnricher} instances to their source types.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@Extension
|
||||||
|
@Singleton
|
||||||
|
public final class HalEnricherRegistry {
|
||||||
|
|
||||||
|
private final Multimap<Class, HalEnricher> enrichers = HashMultimap.create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new {@link HalEnricher} for the given source type.
|
||||||
|
*
|
||||||
|
* @param sourceType type of json mapping source
|
||||||
|
* @param enricher link enricher instance
|
||||||
|
*/
|
||||||
|
public void register(Class sourceType, HalEnricher enricher) {
|
||||||
|
enrichers.put(sourceType, enricher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all registered {@link HalEnricher} for the given type.
|
||||||
|
*
|
||||||
|
* @param sourceType type of json mapping source
|
||||||
|
* @return all registered enrichers
|
||||||
|
*/
|
||||||
|
public Iterable<HalEnricher> allByType(Class sourceType) {
|
||||||
|
return enrichers.get(sourceType);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
scm-core/src/main/java/sonia/scm/api/v2/resources/Index.java
Normal file
10
scm-core/src/main/java/sonia/scm/api/v2/resources/Index.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Index} object can be used to register a {@link HalEnricher} for the index resource.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public final class Index {
|
||||||
|
}
|
||||||
10
scm-core/src/main/java/sonia/scm/api/v2/resources/Me.java
Normal file
10
scm-core/src/main/java/sonia/scm/api/v2/resources/Me.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Me} object can be used to register a {@link HalEnricher} for the me resource.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public final class Me {
|
||||||
|
}
|
||||||
@@ -22,7 +22,8 @@ import com.github.sdorra.ssp.StaticPermissions;
|
|||||||
@StaticPermissions(
|
@StaticPermissions(
|
||||||
value = "configuration",
|
value = "configuration",
|
||||||
permissions = {"read", "write"},
|
permissions = {"read", "write"},
|
||||||
globalPermissions = {"list"}
|
globalPermissions = {"list"},
|
||||||
|
custom = true, customGlobal = true
|
||||||
)
|
)
|
||||||
public interface Configuration extends PermissionObject {
|
public interface Configuration extends PermissionObject {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,59 @@
|
|||||||
package sonia.scm.filter;
|
package sonia.scm.filter;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import org.slf4j.Logger;
|
||||||
import sonia.scm.util.WebUtil;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.ws.rs.container.ContainerRequestContext;
|
import javax.inject.Inject;
|
||||||
import javax.ws.rs.container.ContainerResponseContext;
|
import javax.inject.Provider;
|
||||||
import javax.ws.rs.container.ContainerResponseFilter;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.ws.rs.ext.Provider;
|
import javax.ws.rs.WebApplicationException;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.ext.WriterInterceptor;
|
||||||
|
import javax.ws.rs.ext.WriterInterceptorContext;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
@Provider
|
@javax.ws.rs.ext.Provider
|
||||||
@Slf4j
|
public class GZipResponseFilter implements WriterInterceptor {
|
||||||
public class GZipResponseFilter implements ContainerResponseFilter {
|
|
||||||
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
|
private static final Logger LOG = LoggerFactory.getLogger(GZipResponseFilter.class);
|
||||||
if (WebUtil.isGzipSupported(requestContext::getHeaderString)) {
|
|
||||||
log.trace("compress output with gzip");
|
private final Provider<HttpServletRequest> requestProvider;
|
||||||
GZIPOutputStream wrappedResponse = new GZIPOutputStream(responseContext.getEntityStream());
|
|
||||||
responseContext.getHeaders().add("Content-Encoding", "gzip");
|
@Inject
|
||||||
responseContext.setEntityStream(wrappedResponse);
|
public GZipResponseFilter(Provider<HttpServletRequest> requestProvider) {
|
||||||
|
this.requestProvider = requestProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
|
||||||
|
if (isGZipSupported()) {
|
||||||
|
LOG.trace("compress output with gzip");
|
||||||
|
encodeWithGZip(context);
|
||||||
|
} else {
|
||||||
|
context.proceed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encodeWithGZip(WriterInterceptorContext context) throws IOException {
|
||||||
|
context.getHeaders().remove(HttpHeaders.CONTENT_LENGTH);
|
||||||
|
context.getHeaders().add(HttpHeaders.CONTENT_ENCODING, "gzip");
|
||||||
|
|
||||||
|
OutputStream outputStream = context.getOutputStream();
|
||||||
|
GZIPOutputStream compressedOutputStream = new GZIPOutputStream(outputStream);
|
||||||
|
context.setOutputStream(compressedOutputStream);
|
||||||
|
try {
|
||||||
|
context.proceed();
|
||||||
|
} finally {
|
||||||
|
compressedOutputStream.finish();
|
||||||
|
context.setOutputStream(outputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isGZipSupported() {
|
||||||
|
Object encoding = requestProvider.get().getHeader(HttpHeaders.ACCEPT_ENCODING);
|
||||||
|
return encoding != null && encoding.toString().toLowerCase(Locale.ENGLISH).contains("gzip");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,11 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@StaticPermissions(value = "group", globalPermissions = {"create", "list", "autocomplete"})
|
@StaticPermissions(
|
||||||
|
value = "group",
|
||||||
|
globalPermissions = {"create", "list", "autocomplete"},
|
||||||
|
custom = true, customGlobal = true
|
||||||
|
)
|
||||||
@XmlRootElement(name = "groups")
|
@XmlRootElement(name = "groups")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class Group extends BasicPropertiesAware
|
public class Group extends BasicPropertiesAware
|
||||||
|
|||||||
@@ -70,21 +70,9 @@ public final class GroupNames implements Serializable, Iterable<String>
|
|||||||
* Constructs ...
|
* Constructs ...
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public GroupNames()
|
public GroupNames()
|
||||||
{
|
{
|
||||||
this.collection = Collections.EMPTY_LIST;
|
this(Collections.emptyList());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param collection
|
|
||||||
*/
|
|
||||||
public GroupNames(Collection<String> collection)
|
|
||||||
{
|
|
||||||
this.collection = Collections.unmodifiableCollection(collection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,7 +84,30 @@ public final class GroupNames implements Serializable, Iterable<String>
|
|||||||
*/
|
*/
|
||||||
public GroupNames(String groupName, String... groupNames)
|
public GroupNames(String groupName, String... groupNames)
|
||||||
{
|
{
|
||||||
this.collection = Lists.asList(groupName, groupNames);
|
this(Lists.asList(groupName, groupNames));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs ...
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param collection
|
||||||
|
*/
|
||||||
|
public GroupNames(Collection<String> collection)
|
||||||
|
{
|
||||||
|
this(collection, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs ...
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param collection
|
||||||
|
*/
|
||||||
|
public GroupNames(Collection<String> collection, boolean external)
|
||||||
|
{
|
||||||
|
this.collection = Collections.unmodifiableCollection(collection);
|
||||||
|
this.external = external;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -165,7 +176,7 @@ public final class GroupNames implements Serializable, Iterable<String>
|
|||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return Joiner.on(", ").join(collection);
|
return Joiner.on(", ").join(collection) + "(" + (external? "external": "internal") + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
@@ -181,8 +192,13 @@ public final class GroupNames implements Serializable, Iterable<String>
|
|||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
public boolean isExternal() {
|
||||||
|
return external;
|
||||||
|
}
|
||||||
|
|
||||||
|
//~--- fields ---------------------------------------------------------------
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private final Collection<String> collection;
|
private final Collection<String> collection;
|
||||||
|
|
||||||
|
private final boolean external;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ package sonia.scm.net.ahc;
|
|||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.HashMultimap;
|
|
||||||
import com.google.common.collect.LinkedHashMultimap;
|
import com.google.common.collect.LinkedHashMultimap;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ import java.util.List;
|
|||||||
value = "plugin",
|
value = "plugin",
|
||||||
generatedClass = "PluginPermissions",
|
generatedClass = "PluginPermissions",
|
||||||
permissions = {},
|
permissions = {},
|
||||||
globalPermissions = { "read", "manage" }
|
globalPermissions = { "read", "manage" },
|
||||||
|
custom = true, customGlobal = true
|
||||||
)
|
)
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@XmlRootElement(name = "plugin-information")
|
@XmlRootElement(name = "plugin-information")
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ package sonia.scm.plugin;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.inject.Module;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|||||||
@@ -229,49 +229,10 @@ public abstract class AbstactImportHandler implements AdvancedImportHandler
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param manager
|
|
||||||
* @param repositoryName
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private void importRepository(RepositoryManager manager,
|
|
||||||
String repositoryName)
|
|
||||||
throws IOException {
|
|
||||||
Repository repository =
|
|
||||||
createRepository(getRepositoryDirectory(repositoryName), repositoryName);
|
|
||||||
|
|
||||||
if (logger.isInfoEnabled())
|
|
||||||
{
|
|
||||||
logger.info("import repository {} of type {}", repositoryName,
|
|
||||||
getTypeName());
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.importRepository(repository);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param repositoryName
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private File getRepositoryDirectory(String repositoryName)
|
|
||||||
{
|
|
||||||
return new File(
|
|
||||||
getRepositoryHandler().getConfig().getRepositoryDirectory(),
|
|
||||||
repositoryName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ package sonia.scm.repository;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sonia.scm.NotSupportedFeatureException;
|
import sonia.scm.FeatureNotSupportedException;
|
||||||
import sonia.scm.SCMContextProvider;
|
import sonia.scm.SCMContextProvider;
|
||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
|
||||||
@@ -72,9 +72,11 @@ public abstract class AbstractRepositoryHandler<C extends RepositoryConfig>
|
|||||||
*
|
*
|
||||||
* @param storeFactory
|
* @param storeFactory
|
||||||
*/
|
*/
|
||||||
protected AbstractRepositoryHandler(ConfigurationStoreFactory storeFactory)
|
protected AbstractRepositoryHandler(ConfigurationStoreFactory storeFactory) {
|
||||||
{
|
this.store = storeFactory
|
||||||
this.store = storeFactory.getStore(getConfigClass(), getType().getName());
|
.withType(getConfigClass())
|
||||||
|
.withName(getType().getName())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
@@ -165,12 +167,12 @@ public abstract class AbstractRepositoryHandler<C extends RepositoryConfig>
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*
|
*
|
||||||
* @throws NotSupportedFeatureException
|
* @throws FeatureNotSupportedException
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ImportHandler getImportHandler() throws NotSupportedFeatureException
|
public ImportHandler getImportHandler()
|
||||||
{
|
{
|
||||||
throw new NotSupportedFeatureException("import");
|
throw new FeatureNotSupportedException("import");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,18 +34,14 @@ package sonia.scm.repository;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.base.Throwables;
|
|
||||||
import com.google.common.io.Resources;
|
import com.google.common.io.Resources;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.AlreadyExistsException;
|
|
||||||
import sonia.scm.ConfigurationException;
|
import sonia.scm.ConfigurationException;
|
||||||
import sonia.scm.ContextEntry;
|
|
||||||
import sonia.scm.io.CommandResult;
|
import sonia.scm.io.CommandResult;
|
||||||
import sonia.scm.io.ExtendedCommand;
|
import sonia.scm.io.ExtendedCommand;
|
||||||
import sonia.scm.io.FileSystem;
|
import sonia.scm.plugin.PluginLoader;
|
||||||
import sonia.scm.store.ConfigurationStoreFactory;
|
import sonia.scm.store.ConfigurationStoreFactory;
|
||||||
import sonia.scm.util.IOUtil;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -62,9 +58,8 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
|||||||
|
|
||||||
public static final String DEFAULT_VERSION_INFORMATION = "unknown";
|
public static final String DEFAULT_VERSION_INFORMATION = "unknown";
|
||||||
|
|
||||||
public static final String DIRECTORY_REPOSITORY = "repositories";
|
|
||||||
|
|
||||||
public static final String DOT = ".";
|
public static final String DOT = ".";
|
||||||
|
static final String REPOSITORIES_NATIVE_DIRECTORY = "data";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the logger for AbstractSimpleRepositoryHandler
|
* the logger for AbstractSimpleRepositoryHandler
|
||||||
@@ -72,70 +67,31 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
|||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class);
|
LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class);
|
||||||
|
|
||||||
private FileSystem fileSystem;
|
private final RepositoryLocationResolver repositoryLocationResolver;
|
||||||
|
private final PluginLoader pluginLoader;
|
||||||
|
|
||||||
public AbstractSimpleRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
public AbstractSimpleRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||||
FileSystem fileSystem) {
|
RepositoryLocationResolver repositoryLocationResolver,
|
||||||
|
PluginLoader pluginLoader) {
|
||||||
super(storeFactory);
|
super(storeFactory);
|
||||||
this.fileSystem = fileSystem;
|
this.repositoryLocationResolver = repositoryLocationResolver;
|
||||||
|
this.pluginLoader = pluginLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Repository create(Repository repository) {
|
public Repository create(Repository repository) {
|
||||||
File directory = getDirectory(repository);
|
File nativeDirectory = resolveNativeDirectory(repository.getId());
|
||||||
|
|
||||||
if (directory.exists()) {
|
|
||||||
throw new AlreadyExistsException(repository);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkPath(directory, repository);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fileSystem.create(directory);
|
create(repository, nativeDirectory);
|
||||||
create(repository, directory);
|
postCreate(repository, nativeDirectory);
|
||||||
postCreate(repository, directory);
|
|
||||||
return repository;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
if (directory.exists()) {
|
|
||||||
logger.warn("delete repository directory {}, because of failed repository creation", directory);
|
|
||||||
try {
|
|
||||||
fileSystem.destroy(directory);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Could not destroy directory", e);
|
throw new InternalRepositoryException(repository, "could not create native repository directory", e);
|
||||||
}
|
}
|
||||||
}
|
return repository;
|
||||||
|
|
||||||
Throwables.propagateIfPossible(ex, AlreadyExistsException.class);
|
|
||||||
// This point will never be reached
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String createResourcePath(Repository repository) {
|
|
||||||
StringBuilder path = new StringBuilder("/");
|
|
||||||
|
|
||||||
path.append(getType().getName()).append("/").append(repository.getId());
|
|
||||||
|
|
||||||
return path.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(Repository repository) {
|
public void delete(Repository repository) {
|
||||||
File directory = getDirectory(repository);
|
|
||||||
|
|
||||||
if (directory.exists()) {
|
|
||||||
try {
|
|
||||||
fileSystem.destroy(directory);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InternalRepositoryException(ContextEntry.ContextBuilder.entity("directory", directory.toString()).in(repository), "could not delete repository directory", e);
|
|
||||||
}
|
|
||||||
cleanupEmptyDirectories(config.getRepositoryDirectory(),
|
|
||||||
directory.getParentFile());
|
|
||||||
} else {
|
|
||||||
logger.warn("repository {} not found", repository.getNamespaceAndName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -144,21 +100,6 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
|||||||
|
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
config = createInitialConfig();
|
config = createInitialConfig();
|
||||||
|
|
||||||
if (config != null) {
|
|
||||||
File repositoryDirectory = config.getRepositoryDirectory();
|
|
||||||
|
|
||||||
if (repositoryDirectory == null) {
|
|
||||||
repositoryDirectory = new File(
|
|
||||||
baseDirectory,
|
|
||||||
DIRECTORY_REPOSITORY.concat(File.separator).concat(
|
|
||||||
getType().getName()));
|
|
||||||
config.setRepositoryDirectory(repositoryDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
IOUtil.mkdirs(repositoryDirectory);
|
|
||||||
storeConfig();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,25 +110,13 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File getDirectory(Repository repository) {
|
public File getDirectory(String repositoryId) {
|
||||||
File directory = null;
|
File directory;
|
||||||
|
|
||||||
if (isConfigured()) {
|
if (isConfigured()) {
|
||||||
File repositoryDirectory = config.getRepositoryDirectory();
|
directory = resolveNativeDirectory(repositoryId);
|
||||||
|
|
||||||
directory = new File(repositoryDirectory, repository.getId());
|
|
||||||
|
|
||||||
if (!IOUtil.isChild(repositoryDirectory, directory)) {
|
|
||||||
StringBuilder msg = new StringBuilder(directory.getPath());
|
|
||||||
|
|
||||||
msg.append("is not a child of ").append(repositoryDirectory.getPath());
|
|
||||||
|
|
||||||
throw new ConfigurationException(msg.toString());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw new ConfigurationException("RepositoryHandler is not configured");
|
throw new ConfigurationException("RepositoryHandler is not configured");
|
||||||
}
|
}
|
||||||
|
|
||||||
return directory;
|
return directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +159,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
|||||||
String content = defaultContent;
|
String content = defaultContent;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URL url = Resources.getResource(resource);
|
URL url = pluginLoader.getUberClassLoader().getResource(resource);
|
||||||
|
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
content = Resources.toString(url, Charsets.UTF_8);
|
content = Resources.toString(url, Charsets.UTF_8);
|
||||||
@@ -242,57 +171,7 @@ public abstract class AbstractSimpleRepositoryHandler<C extends RepositoryConfig
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private File resolveNativeDirectory(String repositoryId) {
|
||||||
* Returns true if the directory is a repository.
|
return repositoryLocationResolver.getPath(repositoryId).resolve(REPOSITORIES_NATIVE_DIRECTORY).toFile();
|
||||||
*
|
|
||||||
* @param directory directory to check
|
|
||||||
* @return true if the directory is a repository
|
|
||||||
* @since 1.9
|
|
||||||
*/
|
|
||||||
protected boolean isRepository(File directory) {
|
|
||||||
return new File(directory, DOT.concat(getType().getName())).exists();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check path for existing repositories
|
|
||||||
*
|
|
||||||
* @param directory repository target directory
|
|
||||||
* @throws RuntimeException when the parent directory already is a repository
|
|
||||||
*/
|
|
||||||
private void checkPath(File directory, Repository repository) {
|
|
||||||
File repositoryDirectory = config.getRepositoryDirectory();
|
|
||||||
File parent = directory.getParentFile();
|
|
||||||
|
|
||||||
while ((parent != null) && !repositoryDirectory.equals(parent)) {
|
|
||||||
logger.trace("check {} for existing repository", parent);
|
|
||||||
|
|
||||||
if (isRepository(parent)) {
|
|
||||||
throw new InternalRepositoryException(repository, "parent path " + parent + " is a repository");
|
|
||||||
}
|
|
||||||
|
|
||||||
parent = parent.getParentFile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanupEmptyDirectories(File baseDirectory, File directory) {
|
|
||||||
if (IOUtil.isChild(baseDirectory, directory)) {
|
|
||||||
if (IOUtil.isEmpty(directory)) {
|
|
||||||
|
|
||||||
// TODO use filesystem
|
|
||||||
if (directory.delete()) {
|
|
||||||
logger.info("successfully deleted directory {}", directory);
|
|
||||||
cleanupEmptyDirectories(baseDirectory, directory.getParentFile());
|
|
||||||
} else {
|
|
||||||
logger.warn("could not delete directory {}", directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
logger.debug("could not remove non empty directory {}", directory);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.warn("directory {} is not a child of {}", directory, baseDirectory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ public abstract class DirectoryHealthCheck implements HealthCheck
|
|||||||
else if (handler instanceof RepositoryDirectoryHandler)
|
else if (handler instanceof RepositoryDirectoryHandler)
|
||||||
{
|
{
|
||||||
File directory =
|
File directory =
|
||||||
((RepositoryDirectoryHandler) handler).getDirectory(repository);
|
((RepositoryDirectoryHandler) handler).getDirectory(repository.getId());
|
||||||
|
|
||||||
if (directory == null)
|
if (directory == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,5 +45,10 @@ public enum Feature
|
|||||||
* The default branch of the repository is a combined branch of all
|
* The default branch of the repository is a combined branch of all
|
||||||
* repository branches.
|
* repository branches.
|
||||||
*/
|
*/
|
||||||
COMBINED_DEFAULT_BRANCH
|
COMBINED_DEFAULT_BRANCH,
|
||||||
|
/**
|
||||||
|
* The repository supports computation of incoming changes (either diff or list of changesets) of one branch
|
||||||
|
* in respect to another target branch.
|
||||||
|
*/
|
||||||
|
INCOMING_REVISION
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import com.google.common.base.CharMatcher;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Location Resolver for File based Repository Storage.
|
||||||
|
* <p>
|
||||||
|
* <b>WARNING:</b> The Locations provided with this class may not be used from the plugins to store any plugin specific files.
|
||||||
|
* <p>
|
||||||
|
* Please use the {@link sonia.scm.store.DataStoreFactory } and the {@link sonia.scm.store.DataStore} classes to store data<br>
|
||||||
|
* Please use the {@link sonia.scm.store.BlobStoreFactory } and the {@link sonia.scm.store.BlobStore} classes to store binary files<br>
|
||||||
|
* Please use the {@link sonia.scm.store.ConfigurationStoreFactory} and the {@link sonia.scm.store.ConfigurationStore} classes to store configurations
|
||||||
|
*
|
||||||
|
* @author Mohamed Karray
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class InitialRepositoryLocationResolver {
|
||||||
|
|
||||||
|
private static final String DEFAULT_REPOSITORY_PATH = "repositories";
|
||||||
|
|
||||||
|
private static final CharMatcher ID_MATCHER = CharMatcher.anyOf("/\\.");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the initial path to repository.
|
||||||
|
*
|
||||||
|
* @param repositoryId id of the repository
|
||||||
|
*
|
||||||
|
* @return initial path of repository
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("squid:S2083") // path traversal is prevented with ID_MATCHER
|
||||||
|
public Path getPath(String repositoryId) {
|
||||||
|
// avoid path traversal attacks
|
||||||
|
checkArgument(ID_MATCHER.matchesNoneOf(repositoryId), "repository id contains invalid characters");
|
||||||
|
return Paths.get(DEFAULT_REPOSITORY_PATH, repositoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DAO used for Repositories accessible by a path
|
||||||
|
*
|
||||||
|
* @author Mohamed Karray
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public interface PathBasedRepositoryDAO extends RepositoryDAO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current path of the repository for the given id.
|
||||||
|
* This works for existing repositories only, not for repositories that should be created.
|
||||||
|
*/
|
||||||
|
Path getPath(String repositoryId) ;
|
||||||
|
}
|
||||||
@@ -62,13 +62,13 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
@StaticPermissions(
|
@StaticPermissions(
|
||||||
value = "repository",
|
value = "repository",
|
||||||
permissions = {"read", "modify", "delete", "healthCheck", "pull", "push", "permissionRead", "permissionWrite"}
|
permissions = {"read", "modify", "delete", "healthCheck", "pull", "push", "permissionRead", "permissionWrite"},
|
||||||
|
custom = true, customGlobal = true
|
||||||
)
|
)
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@XmlRootElement(name = "repositories")
|
@XmlRootElement(name = "repositories")
|
||||||
public class Repository extends BasicPropertiesAware implements ModelObject, PermissionObject{
|
public class Repository extends BasicPropertiesAware implements ModelObject, PermissionObject{
|
||||||
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 3486560714961909711L;
|
private static final long serialVersionUID = 3486560714961909711L;
|
||||||
|
|
||||||
private String contact;
|
private String contact;
|
||||||
@@ -81,7 +81,8 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
private Long lastModified;
|
private Long lastModified;
|
||||||
private String namespace;
|
private String namespace;
|
||||||
private String name;
|
private String name;
|
||||||
private final Set<Permission> permissions = new HashSet<>();
|
@XmlElement(name = "permission")
|
||||||
|
private final Set<RepositoryPermission> permissions = new HashSet<>();
|
||||||
@XmlElement(name = "public")
|
@XmlElement(name = "public")
|
||||||
private boolean publicReadable = false;
|
private boolean publicReadable = false;
|
||||||
private boolean archived = false;
|
private boolean archived = false;
|
||||||
@@ -122,7 +123,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
* @param permissions permissions for specific users and groups.
|
* @param permissions permissions for specific users and groups.
|
||||||
*/
|
*/
|
||||||
public Repository(String id, String type, String namespace, String name, String contact,
|
public Repository(String id, String type, String namespace, String name, String contact,
|
||||||
String description, Permission... permissions) {
|
String description, RepositoryPermission... permissions) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.namespace = namespace;
|
this.namespace = namespace;
|
||||||
@@ -201,7 +202,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
return new NamespaceAndName(getNamespace(), getName());
|
return new NamespaceAndName(getNamespace(), getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Permission> getPermissions() {
|
public Collection<RepositoryPermission> getPermissions() {
|
||||||
return Collections.unmodifiableCollection(permissions);
|
return Collections.unmodifiableCollection(permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,16 +298,16 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPermissions(Collection<Permission> permissions) {
|
public void setPermissions(Collection<RepositoryPermission> permissions) {
|
||||||
this.permissions.clear();
|
this.permissions.clear();
|
||||||
this.permissions.addAll(permissions);
|
this.permissions.addAll(permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPermission(Permission newPermission) {
|
public void addPermission(RepositoryPermission newPermission) {
|
||||||
this.permissions.add(newPermission);
|
this.permissions.add(newPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePermission(Permission permission) {
|
public void removePermission(RepositoryPermission permission) {
|
||||||
this.permissions.remove(permission);
|
this.permissions.remove(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
* <p>
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions are met:
|
* modification, are permitted provided that the following conditions are met:
|
||||||
*
|
* <p>
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
* this list of conditions and the following disclaimer.
|
* this list of conditions and the following disclaimer.
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
* contributors may be used to endorse or promote products derived from this
|
* contributors may be used to endorse or promote products derived from this
|
||||||
* software without specific prior written permission.
|
* software without specific prior written permission.
|
||||||
*
|
* <p>
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
@@ -24,13 +24,11 @@
|
|||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
* 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
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*
|
* <p>
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
import sonia.scm.Validateable;
|
import sonia.scm.Validateable;
|
||||||
@@ -38,7 +36,6 @@ import sonia.scm.config.Configuration;
|
|||||||
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import javax.xml.bind.annotation.XmlTransient;
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic {@link Repository} configuration class.
|
* Basic {@link Repository} configuration class.
|
||||||
@@ -46,20 +43,10 @@ import java.io.File;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@XmlRootElement
|
@XmlRootElement
|
||||||
public abstract class RepositoryConfig implements Validateable, Configuration
|
public abstract class RepositoryConfig implements Validateable, Configuration {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the directory for the repositories.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return directory for the repositories
|
|
||||||
*/
|
|
||||||
public File getRepositoryDirectory()
|
|
||||||
{
|
|
||||||
return repositoryDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/** true if the plugin is disabled */
|
||||||
|
private boolean disabled = false;
|
||||||
/**
|
/**
|
||||||
* Returns true if the plugin is disabled.
|
* Returns true if the plugin is disabled.
|
||||||
*
|
*
|
||||||
@@ -67,8 +54,7 @@ public abstract class RepositoryConfig implements Validateable, Configuration
|
|||||||
* @return true if the plugin is disabled
|
* @return true if the plugin is disabled
|
||||||
* @since 1.13
|
* @since 1.13
|
||||||
*/
|
*/
|
||||||
public boolean isDisabled()
|
public boolean isDisabled() {
|
||||||
{
|
|
||||||
return disabled;
|
return disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,9 +65,8 @@ public abstract class RepositoryConfig implements Validateable, Configuration
|
|||||||
* @return true if the configuration object is valid
|
* @return true if the configuration object is valid
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid()
|
public boolean isValid() {
|
||||||
{
|
return true;
|
||||||
return repositoryDirectory != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
//~--- set methods ----------------------------------------------------------
|
||||||
@@ -93,29 +78,11 @@ public abstract class RepositoryConfig implements Validateable, Configuration
|
|||||||
* @param disabled
|
* @param disabled
|
||||||
* @since 1.13
|
* @since 1.13
|
||||||
*/
|
*/
|
||||||
public void setDisabled(boolean disabled)
|
public void setDisabled(boolean disabled) {
|
||||||
{
|
|
||||||
this.disabled = disabled;
|
this.disabled = disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the directory for the repositories
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param repositoryDirectory directory for repositories
|
|
||||||
*/
|
|
||||||
public void setRepositoryDirectory(File repositoryDirectory)
|
|
||||||
{
|
|
||||||
this.repositoryDirectory = repositoryDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** true if the plugin is disabled */
|
|
||||||
private boolean disabled = false;
|
|
||||||
|
|
||||||
/** directory for repositories */
|
|
||||||
private File repositoryDirectory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the identifier of the concrete {@link RepositoryConfig} when checking permissions of an object.
|
* Specifies the identifier of the concrete {@link RepositoryConfig} when checking permissions of an object.
|
||||||
|
|||||||
@@ -40,8 +40,11 @@ import java.io.File;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 1.36
|
* @since 1.36
|
||||||
*/
|
*/
|
||||||
public interface RepositoryDirectoryHandler extends RepositoryHandler
|
public interface RepositoryDirectoryHandler extends RepositoryHandler {
|
||||||
{
|
|
||||||
|
|
||||||
public File getDirectory(Repository repository);
|
/**
|
||||||
|
* Get the current directory of the repository for the given id.
|
||||||
|
* @return the current directory of the given repository
|
||||||
|
*/
|
||||||
|
File getDirectory(String repositoryId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ package sonia.scm.repository;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import sonia.scm.Handler;
|
import sonia.scm.Handler;
|
||||||
import sonia.scm.NotSupportedFeatureException;
|
import sonia.scm.FeatureNotSupportedException;
|
||||||
import sonia.scm.plugin.ExtensionPoint;
|
import sonia.scm.plugin.ExtensionPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,17 +50,6 @@ public interface RepositoryHandler
|
|||||||
extends Handler<Repository>
|
extends Handler<Repository>
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the resource path for the given {@link Repository}.
|
|
||||||
* The resource path is part of the {@link Repository} url.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param repository given {@link Repository}
|
|
||||||
* @return resource path of the {@link Repository}
|
|
||||||
*/
|
|
||||||
public String createResourcePath(Repository repository);
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,9 +59,9 @@ public interface RepositoryHandler
|
|||||||
* @return {@link ImportHandler} for the repository type of this handler
|
* @return {@link ImportHandler} for the repository type of this handler
|
||||||
* @since 1.12
|
* @since 1.12
|
||||||
*
|
*
|
||||||
* @throws NotSupportedFeatureException
|
* @throws FeatureNotSupportedException
|
||||||
*/
|
*/
|
||||||
public ImportHandler getImportHandler() throws NotSupportedFeatureException;
|
public ImportHandler getImportHandler() throws FeatureNotSupportedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns informations about the version of the RepositoryHandler.
|
* Returns informations about the version of the RepositoryHandler.
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import sonia.scm.SCMContextProvider;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Location Resolver for File based Repository Storage.
|
||||||
|
* <p>
|
||||||
|
* <b>WARNING:</b> The Locations provided with this class may not be used from the plugins to store any plugin specific files.
|
||||||
|
* <p>
|
||||||
|
* Please use the {@link sonia.scm.store.DataStoreFactory } and the {@link sonia.scm.store.DataStore} classes to store data<br>
|
||||||
|
* Please use the {@link sonia.scm.store.BlobStoreFactory } and the {@link sonia.scm.store.BlobStore} classes to store binary files<br>
|
||||||
|
* Please use the {@link sonia.scm.store.ConfigurationStoreFactory} and the {@link sonia.scm.store.ConfigurationStore} classes to store configurations
|
||||||
|
*
|
||||||
|
* @author Mohamed Karray
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class RepositoryLocationResolver {
|
||||||
|
|
||||||
|
private final SCMContextProvider contextProvider;
|
||||||
|
private final RepositoryDAO repositoryDAO;
|
||||||
|
private final InitialRepositoryLocationResolver initialRepositoryLocationResolver;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RepositoryLocationResolver(SCMContextProvider contextProvider, RepositoryDAO repositoryDAO, InitialRepositoryLocationResolver initialRepositoryLocationResolver) {
|
||||||
|
this.contextProvider = contextProvider;
|
||||||
|
this.repositoryDAO = repositoryDAO;
|
||||||
|
this.initialRepositoryLocationResolver = initialRepositoryLocationResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path to the repository.
|
||||||
|
*
|
||||||
|
* @param repositoryId repository id
|
||||||
|
*
|
||||||
|
* @return path of repository
|
||||||
|
*/
|
||||||
|
public Path getPath(String repositoryId) {
|
||||||
|
Path path;
|
||||||
|
|
||||||
|
if (repositoryDAO instanceof PathBasedRepositoryDAO) {
|
||||||
|
path = ((PathBasedRepositoryDAO) repositoryDAO).getPath(repositoryId);
|
||||||
|
} else {
|
||||||
|
path = initialRepositoryLocationResolver.getPath(repositoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return contextProvider.resolve(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
public class RepositoryPathNotFoundException extends Exception {
|
||||||
|
|
||||||
|
public static final String REPOSITORY_PATH_NOT_FOUND = "Repository path not found";
|
||||||
|
|
||||||
|
public RepositoryPathNotFoundException() {
|
||||||
|
super(REPOSITORY_PATH_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -41,8 +41,15 @@ import sonia.scm.security.PermissionObject;
|
|||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.unmodifiableSet;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -53,73 +60,38 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "permissions")
|
@XmlRootElement(name = "permissions")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class Permission implements PermissionObject, Serializable
|
public class RepositoryPermission implements PermissionObject, Serializable
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final long serialVersionUID = -2915175031430884040L;
|
private static final long serialVersionUID = -2915175031430884040L;
|
||||||
|
|
||||||
private boolean groupPermission = false;
|
private boolean groupPermission = false;
|
||||||
private String name;
|
private String name;
|
||||||
private PermissionType type = PermissionType.READ;
|
@XmlElement(name = "verb")
|
||||||
|
private Set<String> verbs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new {@link Permission}.
|
* Constructs a new {@link RepositoryPermission}.
|
||||||
* This constructor is used by JAXB.
|
* This constructor is used by JAXB and mapstruct.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public Permission() {}
|
public RepositoryPermission() {}
|
||||||
|
|
||||||
/**
|
public RepositoryPermission(String name, Collection<String> verbs, boolean groupPermission)
|
||||||
* Constructs a new {@link Permission} with type = {@link PermissionType#READ}
|
|
||||||
* for the specified user.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name name of the user
|
|
||||||
*/
|
|
||||||
public Permission(String name)
|
|
||||||
{
|
{
|
||||||
this();
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs));
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new {@link Permission} with the specified type for
|
|
||||||
* the given user.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name name of the user
|
|
||||||
* @param type type of the permission
|
|
||||||
*/
|
|
||||||
public Permission(String name, PermissionType type)
|
|
||||||
{
|
|
||||||
this(name);
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new {@link Permission} with the specified type for
|
|
||||||
* the given user or group.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name name of the user or group
|
|
||||||
* @param type type of the permission
|
|
||||||
* @param groupPermission true if the permission is a permission for a group
|
|
||||||
*/
|
|
||||||
public Permission(String name, PermissionType type, boolean groupPermission)
|
|
||||||
{
|
|
||||||
this(name, type);
|
|
||||||
this.groupPermission = groupPermission;
|
this.groupPermission = groupPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the {@link Permission} is the same as the obj argument.
|
* Returns true if the {@link RepositoryPermission} is the same as the obj argument.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param obj the reference object with which to compare
|
* @param obj the reference object with which to compare
|
||||||
*
|
*
|
||||||
* @return true if the {@link Permission} is the same as the obj argument
|
* @return true if the {@link RepositoryPermission} is the same as the obj argument
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj)
|
public boolean equals(Object obj)
|
||||||
@@ -134,23 +106,26 @@ public class Permission implements PermissionObject, Serializable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Permission other = (Permission) obj;
|
final RepositoryPermission other = (RepositoryPermission) obj;
|
||||||
|
|
||||||
return Objects.equal(name, other.name)
|
return Objects.equal(name, other.name)
|
||||||
&& Objects.equal(type, other.type)
|
&& verbs.containsAll(other.verbs)
|
||||||
|
&& verbs.size() == other.verbs.size()
|
||||||
&& Objects.equal(groupPermission, other.groupPermission);
|
&& Objects.equal(groupPermission, other.groupPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the hash code value for the {@link Permission}.
|
* Returns the hash code value for the {@link RepositoryPermission}.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @return the hash code value for the {@link Permission}
|
* @return the hash code value for the {@link RepositoryPermission}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
{
|
{
|
||||||
return Objects.hashCode(name, type, groupPermission);
|
// Normally we do not have a log of repository permissions having the same size of verbs, but different content.
|
||||||
|
// Therefore we do not use the verbs themselves for the hash code but only the number of verbs.
|
||||||
|
return Objects.hashCode(name, verbs.size(), groupPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -160,7 +135,7 @@ public class Permission implements PermissionObject, Serializable
|
|||||||
//J-
|
//J-
|
||||||
return MoreObjects.toStringHelper(this)
|
return MoreObjects.toStringHelper(this)
|
||||||
.add("name", name)
|
.add("name", name)
|
||||||
.add("type", type)
|
.add("verbs", verbs)
|
||||||
.add("groupPermission", groupPermission)
|
.add("groupPermission", groupPermission)
|
||||||
.toString();
|
.toString();
|
||||||
//J+
|
//J+
|
||||||
@@ -181,14 +156,14 @@ public class Permission implements PermissionObject, Serializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link PermissionType} of the permission.
|
* Returns the verb of the permission.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @return {@link PermissionType} of the permission
|
* @return verb of the permission
|
||||||
*/
|
*/
|
||||||
public PermissionType getType()
|
public Collection<String> getVerbs()
|
||||||
{
|
{
|
||||||
return type;
|
return verbs == null? emptyList(): verbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,13 +203,13 @@ public class Permission implements PermissionObject, Serializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the type of the permission.
|
* Sets the verb of the permission.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param type type of the permission
|
* @param verbs verbs of the permission
|
||||||
*/
|
*/
|
||||||
public void setType(PermissionType type)
|
public void setVerbs(Collection<String> verbs)
|
||||||
{
|
{
|
||||||
this.type = type;
|
this.verbs = unmodifiableSet(new LinkedHashSet<>(verbs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. 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.
|
|
||||||
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.repository;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import sonia.scm.io.DirectoryFileFilter;
|
|
||||||
import sonia.scm.util.IOUtil;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
* @since 1.11
|
|
||||||
*/
|
|
||||||
public final class RepositoryUtil {
|
|
||||||
|
|
||||||
private RepositoryUtil() {}
|
|
||||||
|
|
||||||
public static List<File> searchRepositoryDirectories(File directory, String... names) {
|
|
||||||
List<File> repositories = new ArrayList<>();
|
|
||||||
|
|
||||||
searchRepositoryDirectories(repositories, directory, Arrays.asList(names));
|
|
||||||
|
|
||||||
return repositories;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("squid:S2083") // ignore, because the path is validated at {@link #getRepositoryId(File, File)}
|
|
||||||
public static String getRepositoryId(AbstractRepositoryHandler handler, String directoryPath) throws IOException {
|
|
||||||
return getRepositoryId(handler.getConfig().getRepositoryDirectory(), new File(directoryPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getRepositoryId(AbstractRepositoryHandler handler, File directory) throws IOException {
|
|
||||||
return getRepositoryId(handler.getConfig(), directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getRepositoryId(RepositoryConfig config, File directory) throws IOException {
|
|
||||||
return getRepositoryId(config.getRepositoryDirectory(), directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getRepositoryId(File baseDirectory, File directory) throws IOException {
|
|
||||||
String path = directory.getCanonicalPath();
|
|
||||||
String basePath = baseDirectory.getCanonicalPath();
|
|
||||||
|
|
||||||
Preconditions.checkArgument(
|
|
||||||
path.startsWith(basePath),
|
|
||||||
"repository path %s is not in the main repository path %s", path, basePath
|
|
||||||
);
|
|
||||||
|
|
||||||
String id = IOUtil.trimSeperatorChars(path.substring(basePath.length()));
|
|
||||||
|
|
||||||
Preconditions.checkArgument(
|
|
||||||
!id.contains("\\") && !id.contains("/"),
|
|
||||||
"got illegal repository directory with separators in id: %s", path
|
|
||||||
);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void searchRepositoryDirectories(List<File> repositories, File directory, List<String> names) {
|
|
||||||
boolean found = false;
|
|
||||||
|
|
||||||
for (String name : names) {
|
|
||||||
if (new File(directory, name).exists()) {
|
|
||||||
found = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
repositories.add(directory);
|
|
||||||
} else {
|
|
||||||
File[] directories = directory.listFiles(DirectoryFileFilter.instance);
|
|
||||||
|
|
||||||
if (directories != null) {
|
|
||||||
for (File d : directories) {
|
|
||||||
searchRepositoryDirectories(repositories, d, names);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -38,6 +38,8 @@ package sonia.scm.repository.api;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.FeatureNotSupportedException;
|
||||||
|
import sonia.scm.repository.Feature;
|
||||||
import sonia.scm.repository.spi.DiffCommand;
|
import sonia.scm.repository.spi.DiffCommand;
|
||||||
import sonia.scm.repository.spi.DiffCommandRequest;
|
import sonia.scm.repository.spi.DiffCommandRequest;
|
||||||
import sonia.scm.util.IOUtil;
|
import sonia.scm.util.IOUtil;
|
||||||
@@ -45,6 +47,7 @@ import sonia.scm.util.IOUtil;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -85,10 +88,12 @@ public final class DiffCommandBuilder
|
|||||||
* only be called from the {@link RepositoryService}.
|
* only be called from the {@link RepositoryService}.
|
||||||
*
|
*
|
||||||
* @param diffCommand implementation of {@link DiffCommand}
|
* @param diffCommand implementation of {@link DiffCommand}
|
||||||
|
* @param supportedFeatures The supported features of the provider
|
||||||
*/
|
*/
|
||||||
DiffCommandBuilder(DiffCommand diffCommand)
|
DiffCommandBuilder(DiffCommand diffCommand, Set<Feature> supportedFeatures)
|
||||||
{
|
{
|
||||||
this.diffCommand = diffCommand;
|
this.diffCommand = diffCommand;
|
||||||
|
this.supportedFeatures = supportedFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -174,7 +179,8 @@ public final class DiffCommandBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the difference only for the given revision.
|
* Show the difference only for the given revision or (using {@link #setAncestorChangeset(String)}) between this
|
||||||
|
* and another revision.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param revision revision for difference
|
* @param revision revision for difference
|
||||||
@@ -187,6 +193,22 @@ public final class DiffCommandBuilder
|
|||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Compute the incoming changes of the branch set with {@link #setRevision(String)} in respect to the changeset given
|
||||||
|
* here. In other words: What changes would be new to the ancestor changeset given here when the branch would
|
||||||
|
* be merged into it. Requires feature {@link sonia.scm.repository.Feature#INCOMING_REVISION}!
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public DiffCommandBuilder setAncestorChangeset(String revision)
|
||||||
|
{
|
||||||
|
if (!supportedFeatures.contains(Feature.INCOMING_REVISION)) {
|
||||||
|
throw new FeatureNotSupportedException(Feature.INCOMING_REVISION.name());
|
||||||
|
}
|
||||||
|
request.setAncestorChangeset(revision);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
@@ -215,6 +237,7 @@ public final class DiffCommandBuilder
|
|||||||
|
|
||||||
/** implementation of the diff command */
|
/** implementation of the diff command */
|
||||||
private final DiffCommand diffCommand;
|
private final DiffCommand diffCommand;
|
||||||
|
private Set<Feature> supportedFeatures;
|
||||||
|
|
||||||
/** request for the diff command implementation */
|
/** request for the diff command implementation */
|
||||||
private final DiffCommandRequest request = new DiffCommandRequest();
|
private final DiffCommandRequest request = new DiffCommandRequest();
|
||||||
|
|||||||
@@ -39,12 +39,11 @@ import org.apache.shiro.subject.Subject;
|
|||||||
import sonia.scm.cache.CacheManager;
|
import sonia.scm.cache.CacheManager;
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.ChangesetPagingResult;
|
import sonia.scm.repository.ChangesetPagingResult;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.PreProcessorUtil;
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.spi.IncomingCommand;
|
import sonia.scm.repository.spi.IncomingCommand;
|
||||||
import sonia.scm.repository.spi.IncomingCommandRequest;
|
import sonia.scm.repository.spi.IncomingCommandRequest;
|
||||||
import sonia.scm.security.RepositoryPermission;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@@ -94,8 +93,7 @@ public final class IncomingCommandBuilder
|
|||||||
{
|
{
|
||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
|
||||||
subject.checkPermission(new RepositoryPermission(remoteRepository,
|
subject.isPermitted(RepositoryPermissions.pull(remoteRepository).asShiroString());
|
||||||
PermissionType.READ));
|
|
||||||
|
|
||||||
request.setRemoteRepository(remoteRepository);
|
request.setRemoteRepository(remoteRepository);
|
||||||
|
|
||||||
|
|||||||
@@ -39,10 +39,12 @@ import com.google.common.base.Objects;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.FeatureNotSupportedException;
|
||||||
import sonia.scm.cache.Cache;
|
import sonia.scm.cache.Cache;
|
||||||
import sonia.scm.cache.CacheManager;
|
import sonia.scm.cache.CacheManager;
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.ChangesetPagingResult;
|
import sonia.scm.repository.ChangesetPagingResult;
|
||||||
|
import sonia.scm.repository.Feature;
|
||||||
import sonia.scm.repository.PreProcessorUtil;
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryCacheKey;
|
import sonia.scm.repository.RepositoryCacheKey;
|
||||||
@@ -51,6 +53,7 @@ import sonia.scm.repository.spi.LogCommandRequest;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -104,19 +107,20 @@ public final class LogCommandBuilder
|
|||||||
/**
|
/**
|
||||||
* Constructs a new {@link LogCommandBuilder}, this constructor should
|
* Constructs a new {@link LogCommandBuilder}, this constructor should
|
||||||
* only be called from the {@link RepositoryService}.
|
* only be called from the {@link RepositoryService}.
|
||||||
*
|
|
||||||
* @param cacheManager cache manager
|
* @param cacheManager cache manager
|
||||||
* @param logCommand implementation of the {@link LogCommand}
|
* @param logCommand implementation of the {@link LogCommand}
|
||||||
* @param repository repository to query
|
* @param repository repository to query
|
||||||
* @param preProcessorUtil
|
* @param preProcessorUtil
|
||||||
|
* @param supportedFeatures The supported features of the provider
|
||||||
*/
|
*/
|
||||||
LogCommandBuilder(CacheManager cacheManager, LogCommand logCommand,
|
LogCommandBuilder(CacheManager cacheManager, LogCommand logCommand,
|
||||||
Repository repository, PreProcessorUtil preProcessorUtil)
|
Repository repository, PreProcessorUtil preProcessorUtil, Set<Feature> supportedFeatures)
|
||||||
{
|
{
|
||||||
this.cache = cacheManager.getCache(CACHE_NAME);
|
this.cache = cacheManager.getCache(CACHE_NAME);
|
||||||
this.logCommand = logCommand;
|
this.logCommand = logCommand;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.preProcessorUtil = preProcessorUtil;
|
this.preProcessorUtil = preProcessorUtil;
|
||||||
|
this.supportedFeatures = supportedFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -397,7 +401,17 @@ public final class LogCommandBuilder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the incoming changes of the branch set with {@link #setBranch(String)} in respect to the changeset given
|
||||||
|
* here. In other words: What changesets would be new to the ancestor changeset given here when the branch would
|
||||||
|
* be merged into it. Requires feature {@link sonia.scm.repository.Feature#INCOMING_REVISION}!
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
public LogCommandBuilder setAncestorChangeset(String ancestorChangeset) {
|
public LogCommandBuilder setAncestorChangeset(String ancestorChangeset) {
|
||||||
|
if (!supportedFeatures.contains(Feature.INCOMING_REVISION)) {
|
||||||
|
throw new FeatureNotSupportedException(Feature.INCOMING_REVISION.name());
|
||||||
|
}
|
||||||
request.setAncestorChangeset(ancestorChangeset);
|
request.setAncestorChangeset(ancestorChangeset);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -527,6 +541,7 @@ public final class LogCommandBuilder
|
|||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private final PreProcessorUtil preProcessorUtil;
|
private final PreProcessorUtil preProcessorUtil;
|
||||||
|
private Set<Feature> supportedFeatures;
|
||||||
|
|
||||||
/** repository to query */
|
/** repository to query */
|
||||||
private final Repository repository;
|
private final Repository repository;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import sonia.scm.repository.spi.MergeCommandRequest;
|
|||||||
*
|
*
|
||||||
* To actually merge <code>feature_branch</code> into <code>integration_branch</code> do this:
|
* To actually merge <code>feature_branch</code> into <code>integration_branch</code> do this:
|
||||||
* <pre><code>
|
* <pre><code>
|
||||||
* repositoryService.gerMergeCommand()
|
* repositoryService.getMergeCommand()
|
||||||
* .setBranchToMerge("feature_branch")
|
* .setBranchToMerge("feature_branch")
|
||||||
* .setTargetBranch("integration_branch")
|
* .setTargetBranch("integration_branch")
|
||||||
* .executeMerge();
|
* .executeMerge();
|
||||||
@@ -33,7 +33,7 @@ import sonia.scm.repository.spi.MergeCommandRequest;
|
|||||||
*
|
*
|
||||||
* To check whether they can be merged without conflicts beforehand do this:
|
* To check whether they can be merged without conflicts beforehand do this:
|
||||||
* <pre><code>
|
* <pre><code>
|
||||||
* repositoryService.gerMergeCommand()
|
* repositoryService.getMergeCommand()
|
||||||
* .setBranchToMerge("feature_branch")
|
* .setBranchToMerge("feature_branch")
|
||||||
* .setTargetBranch("integration_branch")
|
* .setTargetBranch("integration_branch")
|
||||||
* .dryRun()
|
* .dryRun()
|
||||||
|
|||||||
@@ -34,12 +34,11 @@ import org.apache.shiro.SecurityUtils;
|
|||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import sonia.scm.cache.CacheManager;
|
import sonia.scm.cache.CacheManager;
|
||||||
import sonia.scm.repository.ChangesetPagingResult;
|
import sonia.scm.repository.ChangesetPagingResult;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.PreProcessorUtil;
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.spi.OutgoingCommand;
|
import sonia.scm.repository.spi.OutgoingCommand;
|
||||||
import sonia.scm.repository.spi.OutgoingCommandRequest;
|
import sonia.scm.repository.spi.OutgoingCommandRequest;
|
||||||
import sonia.scm.security.RepositoryPermission;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@@ -84,8 +83,7 @@ public final class OutgoingCommandBuilder
|
|||||||
{
|
{
|
||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
|
||||||
subject.checkPermission(new RepositoryPermission(remoteRepository,
|
subject.isPermitted(RepositoryPermissions.pull(remoteRepository).asShiroString());
|
||||||
PermissionType.READ));
|
|
||||||
|
|
||||||
request.setRemoteRepository(remoteRepository);
|
request.setRemoteRepository(remoteRepository);
|
||||||
|
|
||||||
|
|||||||
@@ -38,11 +38,10 @@ import org.apache.shiro.SecurityUtils;
|
|||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.spi.PullCommand;
|
import sonia.scm.repository.spi.PullCommand;
|
||||||
import sonia.scm.repository.spi.PullCommandRequest;
|
import sonia.scm.repository.spi.PullCommandRequest;
|
||||||
import sonia.scm.security.RepositoryPermission;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -96,9 +95,7 @@ public final class PullCommandBuilder
|
|||||||
public PullResponse pull(String url) throws IOException {
|
public PullResponse pull(String url) throws IOException {
|
||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
//J-
|
//J-
|
||||||
subject.checkPermission(
|
subject.isPermitted(RepositoryPermissions.push(localRepository).asShiroString());
|
||||||
new RepositoryPermission(localRepository, PermissionType.WRITE)
|
|
||||||
);
|
|
||||||
//J+
|
//J+
|
||||||
|
|
||||||
URL remoteUrl = new URL(url);
|
URL remoteUrl = new URL(url);
|
||||||
@@ -124,12 +121,8 @@ public final class PullCommandBuilder
|
|||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
|
||||||
//J-
|
//J-
|
||||||
subject.checkPermission(
|
subject.isPermitted(RepositoryPermissions.push(localRepository).asShiroString());
|
||||||
new RepositoryPermission(localRepository, PermissionType.WRITE)
|
subject.isPermitted(RepositoryPermissions.push(remoteRepository).asShiroString());
|
||||||
);
|
|
||||||
subject.checkPermission(
|
|
||||||
new RepositoryPermission(remoteRepository, PermissionType.READ)
|
|
||||||
);
|
|
||||||
//J+
|
//J+
|
||||||
|
|
||||||
request.reset();
|
request.reset();
|
||||||
|
|||||||
@@ -39,11 +39,10 @@ import org.apache.shiro.SecurityUtils;
|
|||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.spi.PushCommand;
|
import sonia.scm.repository.spi.PushCommand;
|
||||||
import sonia.scm.repository.spi.PushCommandRequest;
|
import sonia.scm.repository.spi.PushCommandRequest;
|
||||||
import sonia.scm.security.RepositoryPermission;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -92,9 +91,7 @@ public final class PushCommandBuilder
|
|||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
|
||||||
//J-
|
//J-
|
||||||
subject.checkPermission(
|
subject.isPermitted(RepositoryPermissions.push(remoteRepository).asShiroString());
|
||||||
new RepositoryPermission(remoteRepository, PermissionType.WRITE)
|
|
||||||
);
|
|
||||||
//J+
|
//J+
|
||||||
|
|
||||||
logger.info("push changes to repository {}", remoteRepository.getId());
|
logger.info("push changes to repository {}", remoteRepository.getId());
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
logger.debug("create diff command for repository {}",
|
logger.debug("create diff command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new DiffCommandBuilder(provider.getDiffCommand());
|
return new DiffCommandBuilder(provider.getDiffCommand(), provider.getSupportedFeatures());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -253,7 +253,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new LogCommandBuilder(cacheManager, provider.getLogCommand(),
|
return new LogCommandBuilder(cacheManager, provider.getLogCommand(),
|
||||||
repository, preProcessorUtil);
|
repository, preProcessorUtil, provider.getSupportedFeatures());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -363,8 +363,8 @@ public final class RepositoryService implements Closeable {
|
|||||||
* by the implementation of the repository service provider.
|
* by the implementation of the repository service provider.
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public MergeCommandBuilder gerMergeCommand() {
|
public MergeCommandBuilder getMergeCommand() {
|
||||||
logger.debug("create unbundle command for repository {}",
|
logger.debug("create merge command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new MergeCommandBuilder(provider.getMergeCommand());
|
return new MergeCommandBuilder(provider.getMergeCommand());
|
||||||
|
|||||||
@@ -3,10 +3,29 @@ package sonia.scm.repository.api;
|
|||||||
import sonia.scm.plugin.ExtensionPoint;
|
import sonia.scm.plugin.ExtensionPoint;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider for scm native protocols.
|
||||||
|
*
|
||||||
|
* @param <T> type of protocol
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
@ExtensionPoint(multi = true)
|
@ExtensionPoint(multi = true)
|
||||||
public interface ScmProtocolProvider<T extends ScmProtocol> {
|
public interface ScmProtocolProvider<T extends ScmProtocol> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns type of repository (e.g.: git, svn, hg, etc.)
|
||||||
|
*
|
||||||
|
* @return name of type
|
||||||
|
*/
|
||||||
String getType();
|
String getType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns protocol for the given repository.
|
||||||
|
*
|
||||||
|
* @param repository repository
|
||||||
|
*
|
||||||
|
* @return protocol for repository
|
||||||
|
*/
|
||||||
T get(Repository repository);
|
T get(Repository repository);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,19 +74,24 @@ public final class HookEventFacade
|
|||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
public HookEventHandler handle(String id) {
|
public HookEventHandler handle(String id) {
|
||||||
return handle(repositoryManagerProvider.get().get(id));
|
Repository repository = repositoryManagerProvider.get().get(id);
|
||||||
|
if (repository == null)
|
||||||
|
{
|
||||||
|
throw notFound(entity("repository", id));
|
||||||
|
}
|
||||||
|
return handle(repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HookEventHandler handle(NamespaceAndName namespaceAndName) {
|
public HookEventHandler handle(NamespaceAndName namespaceAndName) {
|
||||||
return handle(repositoryManagerProvider.get().get(namespaceAndName));
|
Repository repository = repositoryManagerProvider.get().get(namespaceAndName);
|
||||||
|
if (repository == null)
|
||||||
|
{
|
||||||
|
throw notFound(entity(namespaceAndName));
|
||||||
|
}
|
||||||
|
return handle(repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HookEventHandler handle(Repository repository) {
|
public HookEventHandler handle(Repository repository) {
|
||||||
if (repository == null)
|
|
||||||
{
|
|
||||||
throw notFound(entity(repository));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HookEventHandler(repositoryManagerProvider.get(),
|
return new HookEventHandler(repositoryManagerProvider.get(),
|
||||||
hookContextFactory, repository);
|
hookContextFactory, repository);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.google.common.base.Objects;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import sonia.scm.Validateable;
|
import sonia.scm.Validateable;
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
import sonia.scm.util.Util;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An access token can be used to access scm-manager without providing username and password. An {@link AccessToken} can
|
* An access token can be used to access scm-manager without providing username and password. An {@link AccessToken} can
|
||||||
@@ -79,6 +81,20 @@ public interface AccessToken {
|
|||||||
*/
|
*/
|
||||||
Date getExpiration();
|
Date getExpiration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns refresh expiration of token.
|
||||||
|
*
|
||||||
|
* @return refresh expiration
|
||||||
|
*/
|
||||||
|
Optional<Date> getRefreshExpiration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns id of the parent key.
|
||||||
|
*
|
||||||
|
* @return parent key id
|
||||||
|
*/
|
||||||
|
Optional<String> getParentKey();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the scope of the token. The scope is able to reduce the permissions of the subject in the context of this
|
* Returns the scope of the token. The scope is able to reduce the permissions of the subject in the context of this
|
||||||
* token. For example we could issue a token which can only be used to read a single repository. for more informations
|
* token. For example we could issue a token which can only be used to read a single repository. for more informations
|
||||||
@@ -88,6 +104,13 @@ public interface AccessToken {
|
|||||||
*/
|
*/
|
||||||
Scope getScope();
|
Scope getScope();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns name of groups, in which the user should be a member.
|
||||||
|
*
|
||||||
|
* @return name of groups
|
||||||
|
*/
|
||||||
|
Set<String> getGroups();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an optional value of a custom token field.
|
* Returns an optional value of a custom token field.
|
||||||
*
|
*
|
||||||
@@ -104,4 +127,9 @@ public interface AccessToken {
|
|||||||
* @return compact representation
|
* @return compact representation
|
||||||
*/
|
*/
|
||||||
String compact();
|
String compact();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns read only map of all claim keys with their values.
|
||||||
|
*/
|
||||||
|
Map<String, Object> getClaims();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,12 +74,22 @@ public interface AccessTokenBuilder {
|
|||||||
* Sets the expiration for the token.
|
* Sets the expiration for the token.
|
||||||
*
|
*
|
||||||
* @param count expiration count
|
* @param count expiration count
|
||||||
* @param unit expirtation unit
|
* @param unit expiration unit
|
||||||
*
|
*
|
||||||
* @return {@code this}
|
* @return {@code this}
|
||||||
*/
|
*/
|
||||||
AccessTokenBuilder expiresIn(long count, TimeUnit unit);
|
AccessTokenBuilder expiresIn(long count, TimeUnit unit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the time how long this token may be refreshed. Set this to 0 (zero) to disable automatic refresh.
|
||||||
|
*
|
||||||
|
* @param count Time unit count. If set to 0, automatic refresh is disabled.
|
||||||
|
* @param unit time unit
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
AccessTokenBuilder refreshableFor(long count, TimeUnit unit);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduces the permissions of the token by providing a scope.
|
* Reduces the permissions of the token by providing a scope.
|
||||||
*
|
*
|
||||||
@@ -89,6 +99,15 @@ public interface AccessTokenBuilder {
|
|||||||
*/
|
*/
|
||||||
AccessTokenBuilder scope(Scope scope);
|
AccessTokenBuilder scope(Scope scope);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the logged in user as member of the given groups.
|
||||||
|
*
|
||||||
|
* @param groups group names
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
AccessTokenBuilder groups(String... groups);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link AccessToken} with the provided settings.
|
* Creates a new {@link AccessToken} with the provided settings.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates cookies and invalidates access token cookies.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public interface AccessTokenCookieIssuer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a cookie for token authentication and attaches it to the response.
|
||||||
|
*
|
||||||
|
* @param request http servlet request
|
||||||
|
* @param response http servlet response
|
||||||
|
* @param accessToken access token
|
||||||
|
*/
|
||||||
|
void authenticate(HttpServletRequest request, HttpServletResponse response, AccessToken accessToken);
|
||||||
|
/**
|
||||||
|
* Invalidates the authentication cookie.
|
||||||
|
*
|
||||||
|
* @param request http servlet request
|
||||||
|
* @param response http servlet response
|
||||||
|
*/
|
||||||
|
void invalidate(HttpServletRequest request, HttpServletResponse response);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -30,26 +30,25 @@
|
|||||||
*/
|
*/
|
||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import sonia.scm.plugin.ExtensionPoint;
|
import sonia.scm.plugin.ExtensionPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the claims of a jwt token. The validator is called durring authentication
|
* Validates an {@link AccessToken}. The validator is called during authentication
|
||||||
* with a jwt token.
|
* with an {@link AccessToken}.
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@ExtensionPoint
|
@ExtensionPoint
|
||||||
public interface TokenClaimsValidator {
|
public interface AccessTokenValidator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@code true} if the claims is valid. If the token is not valid and the
|
* Returns {@code true} if the {@link AccessToken} is valid. If the token is not valid and the
|
||||||
* method returns {@code false}, the authentication is treated as failed.
|
* method returns {@code false}, the authentication is treated as failed.
|
||||||
*
|
*
|
||||||
* @param claims token claims
|
* @param token the access token to verify
|
||||||
*
|
*
|
||||||
* @return {@code true} if the claims is valid
|
* @return {@code true} if the token is valid
|
||||||
*/
|
*/
|
||||||
boolean validate(Map<String, Object> claims);
|
boolean validate(AccessToken token);
|
||||||
}
|
}
|
||||||
@@ -89,8 +89,12 @@ public class AssignedPermission implements PermissionObject, Serializable
|
|||||||
*/
|
*/
|
||||||
public AssignedPermission(String name, String permission)
|
public AssignedPermission(String name, String permission)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this(name, new PermissionDescriptor(permission));
|
||||||
this.permission = permission;
|
}
|
||||||
|
|
||||||
|
public AssignedPermission(String name, PermissionDescriptor permission)
|
||||||
|
{
|
||||||
|
this(name, false, permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,6 +107,12 @@ public class AssignedPermission implements PermissionObject, Serializable
|
|||||||
*/
|
*/
|
||||||
public AssignedPermission(String name, boolean groupPermission,
|
public AssignedPermission(String name, boolean groupPermission,
|
||||||
String permission)
|
String permission)
|
||||||
|
{
|
||||||
|
this(name, groupPermission, new PermissionDescriptor(permission));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssignedPermission(String name, boolean groupPermission,
|
||||||
|
PermissionDescriptor permission)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.groupPermission = groupPermission;
|
this.groupPermission = groupPermission;
|
||||||
@@ -173,12 +183,9 @@ public class AssignedPermission implements PermissionObject, Serializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the string representation of the permission.
|
* Returns the description of the permission.
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return string representation of the permission
|
|
||||||
*/
|
*/
|
||||||
public String getPermission()
|
public PermissionDescriptor getPermission()
|
||||||
{
|
{
|
||||||
return permission;
|
return permission;
|
||||||
}
|
}
|
||||||
@@ -205,5 +212,5 @@ public class AssignedPermission implements PermissionObject, Serializable
|
|||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/** string representation of the permission */
|
/** string representation of the permission */
|
||||||
private String permission;
|
private PermissionDescriptor permission;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import java.io.Serializable;
|
|||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
@Event
|
@Event
|
||||||
public final class StoredAssignedPermissionEvent implements Serializable
|
public final class AssignedPermissionEvent implements Serializable
|
||||||
{
|
{
|
||||||
|
|
||||||
/** serial version uid */
|
/** serial version uid */
|
||||||
@@ -60,14 +60,14 @@ public final class StoredAssignedPermissionEvent implements Serializable
|
|||||||
//~--- constructors ---------------------------------------------------------
|
//~--- constructors ---------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new StoredAssignedPermissionEvent.
|
* Constructs a new AssignedPermissionEvent.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param type type of the event
|
* @param type type of the event
|
||||||
* @param permission permission object which has changed
|
* @param permission permission object which has changed
|
||||||
*/
|
*/
|
||||||
public StoredAssignedPermissionEvent(HandlerEventType type,
|
public AssignedPermissionEvent(HandlerEventType type,
|
||||||
StoredAssignedPermission permission)
|
AssignedPermission permission)
|
||||||
{
|
{
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.permission = permission;
|
this.permission = permission;
|
||||||
@@ -91,8 +91,8 @@ public final class StoredAssignedPermissionEvent implements Serializable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final StoredAssignedPermissionEvent other =
|
final AssignedPermissionEvent other =
|
||||||
(StoredAssignedPermissionEvent) obj;
|
(AssignedPermissionEvent) obj;
|
||||||
|
|
||||||
return Objects.equal(type, other.type)
|
return Objects.equal(type, other.type)
|
||||||
&& Objects.equal(permission, other.permission);
|
&& Objects.equal(permission, other.permission);
|
||||||
@@ -140,7 +140,7 @@ public final class StoredAssignedPermissionEvent implements Serializable
|
|||||||
*
|
*
|
||||||
* @return changed permission
|
* @return changed permission
|
||||||
*/
|
*/
|
||||||
public StoredAssignedPermission getPermission()
|
public AssignedPermission getPermission()
|
||||||
{
|
{
|
||||||
return permission;
|
return permission;
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ public final class StoredAssignedPermissionEvent implements Serializable
|
|||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** changed permission */
|
/** changed permission */
|
||||||
private StoredAssignedPermission permission;
|
private AssignedPermission permission;
|
||||||
|
|
||||||
/** type of the event */
|
/** type of the event */
|
||||||
private HandlerEventType type;
|
private HandlerEventType type;
|
||||||
@@ -34,6 +34,7 @@ package sonia.scm.security;
|
|||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import org.apache.shiro.authz.AuthorizationInfo;
|
import org.apache.shiro.authz.AuthorizationInfo;
|
||||||
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
import sonia.scm.plugin.ExtensionPoint;
|
import sonia.scm.plugin.ExtensionPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,15 +43,16 @@ import sonia.scm.plugin.ExtensionPoint;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@ExtensionPoint(multi = false)
|
@ExtensionPoint
|
||||||
public interface AuthorizationCollector
|
public interface AuthorizationCollector
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@link AuthorizationInfo} for the authenticated user.
|
* Returns {@link AuthorizationInfo} for the authenticated user.
|
||||||
*
|
*
|
||||||
|
* @param principalCollection collected principals
|
||||||
*
|
*
|
||||||
* @return {@link AuthorizationInfo} for authenticated user
|
* @return {@link AuthorizationInfo} for authenticated user
|
||||||
*/
|
*/
|
||||||
public AuthorizationInfo collect();
|
AuthorizationInfo collect(PrincipalCollection principalCollection);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import com.google.common.base.MoreObjects;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.ImmutableSet.Builder;
|
import com.google.common.collect.ImmutableSet.Builder;
|
||||||
import org.apache.shiro.authc.AuthenticationException;
|
|
||||||
import org.apache.shiro.authc.AuthenticationInfo;
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
import org.apache.shiro.authc.AuthenticationToken;
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
import org.apache.shiro.authc.DisabledAccountException;
|
import org.apache.shiro.authc.DisabledAccountException;
|
||||||
@@ -54,6 +53,8 @@ import sonia.scm.group.GroupNames;
|
|||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserDAO;
|
import sonia.scm.user.UserDAO;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,8 +64,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public final class DAORealmHelper
|
public final class DAORealmHelper {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the logger for DAORealmHelper
|
* the logger for DAORealmHelper
|
||||||
@@ -111,35 +111,35 @@ public final class DAORealmHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Creates {@link AuthenticationInfo} from a {@link UsernamePasswordToken}. The method accepts
|
||||||
|
* {@link AuthenticationInfo} as argument, so that the caller does not need to cast.
|
||||||
*
|
*
|
||||||
|
* @param token authentication token, it must be {@link UsernamePasswordToken}
|
||||||
*
|
*
|
||||||
* @param token
|
* @return authentication info
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws AuthenticationException
|
|
||||||
*/
|
*/
|
||||||
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
|
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) {
|
||||||
checkArgument(token instanceof UsernamePasswordToken, "%s is required", UsernamePasswordToken.class);
|
checkArgument(token instanceof UsernamePasswordToken, "%s is required", UsernamePasswordToken.class);
|
||||||
|
|
||||||
UsernamePasswordToken upt = (UsernamePasswordToken) token;
|
UsernamePasswordToken upt = (UsernamePasswordToken) token;
|
||||||
String principal = upt.getUsername();
|
String principal = upt.getUsername();
|
||||||
|
|
||||||
return getAuthenticationInfo(principal, null, null);
|
return getAuthenticationInfo(principal, null, null, Collections.emptySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Returns a builder for {@link AuthenticationInfo}.
|
||||||
*
|
*
|
||||||
|
* @param principal name of principal (username)
|
||||||
*
|
*
|
||||||
* @param principal
|
* @return authentication info builder
|
||||||
* @param credentials
|
|
||||||
* @param scope
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
public AuthenticationInfo getAuthenticationInfo(String principal, String credentials, Scope scope) {
|
public AuthenticationInfoBuilder authenticationInfoBuilder(String principal) {
|
||||||
|
return new AuthenticationInfoBuilder(principal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private AuthenticationInfo getAuthenticationInfo(String principal, String credentials, Scope scope, Iterable<String> groups) {
|
||||||
checkArgument(!Strings.isNullOrEmpty(principal), "username is required");
|
checkArgument(!Strings.isNullOrEmpty(principal), "username is required");
|
||||||
|
|
||||||
LOG.debug("try to authenticate {}", principal);
|
LOG.debug("try to authenticate {}", principal);
|
||||||
@@ -157,7 +157,7 @@ public final class DAORealmHelper
|
|||||||
|
|
||||||
collection.add(principal, realm);
|
collection.add(principal, realm);
|
||||||
collection.add(user, realm);
|
collection.add(user, realm);
|
||||||
collection.add(collectGroups(principal), realm);
|
collection.add(collectGroups(principal, groups), realm);
|
||||||
collection.add(MoreObjects.firstNonNull(scope, Scope.empty()), realm);
|
collection.add(MoreObjects.firstNonNull(scope, Scope.empty()), realm);
|
||||||
|
|
||||||
String creds = credentials;
|
String creds = credentials;
|
||||||
@@ -171,11 +171,15 @@ public final class DAORealmHelper
|
|||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
private GroupNames collectGroups(String principal) {
|
private GroupNames collectGroups(String principal, Iterable<String> groupNames) {
|
||||||
Builder<String> builder = ImmutableSet.builder();
|
Builder<String> builder = ImmutableSet.builder();
|
||||||
|
|
||||||
builder.add(GroupNames.AUTHENTICATED);
|
builder.add(GroupNames.AUTHENTICATED);
|
||||||
|
|
||||||
|
for (String group : groupNames) {
|
||||||
|
builder.add(group);
|
||||||
|
}
|
||||||
|
|
||||||
for (Group group : groupDAO.getAll()) {
|
for (Group group : groupDAO.getAll()) {
|
||||||
if (group.isMember(principal)) {
|
if (group.isMember(principal)) {
|
||||||
builder.add(group.getName());
|
builder.add(group.getName());
|
||||||
@@ -187,6 +191,69 @@ public final class DAORealmHelper
|
|||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder class for {@link AuthenticationInfo}.
|
||||||
|
*/
|
||||||
|
public class AuthenticationInfoBuilder {
|
||||||
|
|
||||||
|
private final String principal;
|
||||||
|
|
||||||
|
private String credentials;
|
||||||
|
private Scope scope;
|
||||||
|
private Iterable<String> groups = Collections.emptySet();
|
||||||
|
|
||||||
|
private AuthenticationInfoBuilder(String principal) {
|
||||||
|
this.principal = principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With credentials uses the given credentials for the {@link AuthenticationInfo}, this is particularly important
|
||||||
|
* for caching purposes.
|
||||||
|
*
|
||||||
|
* @param credentials credentials such as password
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public AuthenticationInfoBuilder withCredentials(String credentials) {
|
||||||
|
this.credentials = credentials;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With the scope object it is possible to limit the access permissions to scm-manager.
|
||||||
|
*
|
||||||
|
* @param scope scope object
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public AuthenticationInfoBuilder withScope(Scope scope) {
|
||||||
|
this.scope = scope;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With groups adds extra groups, besides those which come from the {@link GroupDAO}, to the authentication info.
|
||||||
|
*
|
||||||
|
* @param groups extra groups
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
public AuthenticationInfoBuilder withGroups(Iterable<String> groups) {
|
||||||
|
this.groups = groups;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build creates the authentication info from the given information.
|
||||||
|
*
|
||||||
|
* @return authentication info
|
||||||
|
*/
|
||||||
|
public AuthenticationInfo build() {
|
||||||
|
return getAuthenticationInfo(principal, credentials, scope, groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static class RetryLimitPasswordMatcher implements CredentialsMatcher {
|
private static class RetryLimitPasswordMatcher implements CredentialsMatcher {
|
||||||
|
|
||||||
private final LoginAttemptHandler loginAttemptHandler;
|
private final LoginAttemptHandler loginAttemptHandler;
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ public class DefaultCipherHandler implements CipherHandler {
|
|||||||
String result = null;
|
String result = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] encodedInput = Base64.getDecoder().decode(value);
|
byte[] encodedInput = Base64.getUrlDecoder().decode(value);
|
||||||
byte[] salt = new byte[SALT_LENGTH];
|
byte[] salt = new byte[SALT_LENGTH];
|
||||||
byte[] encoded = new byte[encodedInput.length - SALT_LENGTH];
|
byte[] encoded = new byte[encodedInput.length - SALT_LENGTH];
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ public class DefaultCipherHandler implements CipherHandler {
|
|||||||
System.arraycopy(salt, 0, result, 0, SALT_LENGTH);
|
System.arraycopy(salt, 0, result, 0, SALT_LENGTH);
|
||||||
System.arraycopy(encodedInput, 0, result, SALT_LENGTH,
|
System.arraycopy(encodedInput, 0, result, SALT_LENGTH,
|
||||||
result.length - SALT_LENGTH);
|
result.length - SALT_LENGTH);
|
||||||
res = new String(Base64.getEncoder().encode(result), ENCODING);
|
res = new String(Base64.getUrlEncoder().encode(result), ENCODING);
|
||||||
} catch (IOException | GeneralSecurityException ex) {
|
} catch (IOException | GeneralSecurityException ex) {
|
||||||
throw new CipherException("could not encode string", ex);
|
throw new CipherException("could not encode string", ex);
|
||||||
}
|
}
|
||||||
|
|||||||
13
scm-core/src/main/java/sonia/scm/security/Permission.java
Normal file
13
scm-core/src/main/java/sonia/scm/security/Permission.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import com.github.sdorra.ssp.PermissionObject;
|
||||||
|
import com.github.sdorra.ssp.StaticPermissions;
|
||||||
|
|
||||||
|
@StaticPermissions(
|
||||||
|
value = "permission",
|
||||||
|
permissions = {},
|
||||||
|
globalPermissions = {"list", "read", "assign"},
|
||||||
|
custom = true, customGlobal = true
|
||||||
|
)
|
||||||
|
public interface Permission extends PermissionObject {
|
||||||
|
}
|
||||||
@@ -39,7 +39,6 @@ import com.google.common.base.Objects;
|
|||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
@@ -67,19 +66,8 @@ public class PermissionDescriptor implements Serializable
|
|||||||
*/
|
*/
|
||||||
public PermissionDescriptor() {}
|
public PermissionDescriptor() {}
|
||||||
|
|
||||||
/**
|
public PermissionDescriptor(String value)
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param displayName
|
|
||||||
* @param description
|
|
||||||
* @param value
|
|
||||||
*/
|
|
||||||
public PermissionDescriptor(String displayName, String description,
|
|
||||||
String value)
|
|
||||||
{
|
{
|
||||||
this.displayName = displayName;
|
|
||||||
this.description = description;
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +91,7 @@ public class PermissionDescriptor implements Serializable
|
|||||||
|
|
||||||
final PermissionDescriptor other = (PermissionDescriptor) obj;
|
final PermissionDescriptor other = (PermissionDescriptor) obj;
|
||||||
|
|
||||||
return Objects.equal(displayName, other.displayName)
|
return Objects.equal(value, other.value);
|
||||||
&& Objects.equal(description, other.description)
|
|
||||||
&& Objects.equal(value, other.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,7 +100,7 @@ public class PermissionDescriptor implements Serializable
|
|||||||
@Override
|
@Override
|
||||||
public int hashCode()
|
public int hashCode()
|
||||||
{
|
{
|
||||||
return Objects.hashCode(displayName, description, value);
|
return value.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,8 +112,6 @@ public class PermissionDescriptor implements Serializable
|
|||||||
|
|
||||||
//J-
|
//J-
|
||||||
return MoreObjects.toStringHelper(this)
|
return MoreObjects.toStringHelper(this)
|
||||||
.add("displayName", displayName)
|
|
||||||
.add("description", description)
|
|
||||||
.add("value", value)
|
.add("value", value)
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
@@ -136,28 +120,6 @@ public class PermissionDescriptor implements Serializable
|
|||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the description of the permission.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return description
|
|
||||||
*/
|
|
||||||
public String getDescription()
|
|
||||||
{
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the display name of the permission.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return display name
|
|
||||||
*/
|
|
||||||
public String getDisplayName()
|
|
||||||
{
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the string representation of the permission.
|
* Returns the string representation of the permission.
|
||||||
*
|
*
|
||||||
@@ -171,13 +133,6 @@ public class PermissionDescriptor implements Serializable
|
|||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** description */
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
/** display name */
|
|
||||||
@XmlElement(name = "display-name")
|
|
||||||
private String displayName;
|
|
||||||
|
|
||||||
/** value */
|
/** value */
|
||||||
private String value;
|
private String value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,230 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. 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.
|
|
||||||
* 3. Neither the name of SCM-Manager; 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 REGENTS 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.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.security;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.base.MoreObjects;
|
|
||||||
import com.google.common.base.Objects;
|
|
||||||
import org.apache.shiro.authz.Permission;
|
|
||||||
import sonia.scm.repository.PermissionType;
|
|
||||||
import sonia.scm.repository.Repository;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class represents the permission to a repository of a user.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
* @since 1.21
|
|
||||||
*/
|
|
||||||
public final class RepositoryPermission
|
|
||||||
implements StringablePermission, Serializable
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type string of the permission
|
|
||||||
* @since 1.31
|
|
||||||
*/
|
|
||||||
public static final String TYPE = "repository";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String WILDCARD = "*";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final long serialVersionUID = 3832804235417228043L;
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param repository
|
|
||||||
* @param permissionType
|
|
||||||
*/
|
|
||||||
public RepositoryPermission(Repository repository,
|
|
||||||
PermissionType permissionType)
|
|
||||||
{
|
|
||||||
this(repository.getId(), permissionType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param repositoryId
|
|
||||||
* @param permissionType
|
|
||||||
*/
|
|
||||||
public RepositoryPermission(String repositoryId,
|
|
||||||
PermissionType permissionType)
|
|
||||||
{
|
|
||||||
this.repositoryId = repositoryId;
|
|
||||||
this.permissionType = permissionType;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param obj
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getClass() != obj.getClass())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final RepositoryPermission other = (RepositoryPermission) obj;
|
|
||||||
|
|
||||||
return Objects.equal(repositoryId, other.repositoryId)
|
|
||||||
&& Objects.equal(permissionType, other.permissionType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int hashCode()
|
|
||||||
{
|
|
||||||
return Objects.hashCode(repositoryId, permissionType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param p
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean implies(Permission p)
|
|
||||||
{
|
|
||||||
boolean result = false;
|
|
||||||
|
|
||||||
if (p instanceof RepositoryPermission)
|
|
||||||
{
|
|
||||||
RepositoryPermission rp = (RepositoryPermission) p;
|
|
||||||
|
|
||||||
//J-
|
|
||||||
result = (repositoryId.equals(WILDCARD) || repositoryId.equals(rp.repositoryId))
|
|
||||||
&& (permissionType.getValue() >= rp.permissionType.getValue());
|
|
||||||
//J+
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
//J-
|
|
||||||
return MoreObjects.toStringHelper(this)
|
|
||||||
.add("repositoryId", repositoryId)
|
|
||||||
.add("permissionType", permissionType)
|
|
||||||
.toString();
|
|
||||||
//J+
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getAsString()
|
|
||||||
{
|
|
||||||
StringBuilder buffer = new StringBuilder(TYPE);
|
|
||||||
|
|
||||||
buffer.append(":").append(repositoryId).append(":").append(permissionType);
|
|
||||||
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public PermissionType getPermissionType()
|
|
||||||
{
|
|
||||||
return permissionType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getRepositoryId()
|
|
||||||
{
|
|
||||||
return repositoryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private PermissionType permissionType;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String repositoryId;
|
|
||||||
}
|
|
||||||
@@ -32,13 +32,8 @@
|
|||||||
|
|
||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
import java.util.Collection;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The SecuritySystem manages global permissions.
|
* The SecuritySystem manages global permissions.
|
||||||
@@ -57,7 +52,7 @@ public interface SecuritySystem
|
|||||||
*
|
*
|
||||||
* @return stored permission
|
* @return stored permission
|
||||||
*/
|
*/
|
||||||
public StoredAssignedPermission addPermission(AssignedPermission permission);
|
void addPermission(AssignedPermission permission);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete stored permission.
|
* Delete stored permission.
|
||||||
@@ -65,51 +60,17 @@ public interface SecuritySystem
|
|||||||
*
|
*
|
||||||
* @param permission permission to be deleted
|
* @param permission permission to be deleted
|
||||||
*/
|
*/
|
||||||
public void deletePermission(StoredAssignedPermission permission);
|
void deletePermission(AssignedPermission permission);
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete stored permission.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param id id of the permission
|
|
||||||
*/
|
|
||||||
public void deletePermission(String id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modify stored permission.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param permission stored permisison
|
|
||||||
*/
|
|
||||||
public void modifyPermission(StoredAssignedPermission permission);
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
|
||||||
* Return all stored permissions.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return stored permission
|
|
||||||
*/
|
|
||||||
public List<StoredAssignedPermission> getAllPermissions();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all available permissions.
|
* Return all available permissions.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @return available permissions
|
* @return available permissions
|
||||||
*/
|
*/
|
||||||
public List<PermissionDescriptor> getAvailablePermissions();
|
Collection<PermissionDescriptor> getAvailablePermissions();
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the stored permission which is stored with the given id.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param id id of the stored permission
|
|
||||||
*
|
|
||||||
* @return stored permission
|
|
||||||
*/
|
|
||||||
public StoredAssignedPermission getPermission(String id);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all stored permissions which are matched by the given
|
* Returns all stored permissions which are matched by the given
|
||||||
@@ -120,6 +81,5 @@ public interface SecuritySystem
|
|||||||
*
|
*
|
||||||
* @return filtered permissions
|
* @return filtered permissions
|
||||||
*/
|
*/
|
||||||
public List<StoredAssignedPermission> getPermissions(
|
Collection<AssignedPermission> getPermissions(Predicate<AssignedPermission> predicate);
|
||||||
Predicate<AssignedPermission> predicate);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,12 @@ import sonia.scm.user.User;
|
|||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.web.security.AdministrationContext;
|
import sonia.scm.web.security.AdministrationContext;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for syncing realms. The class should simplify the creation of realms, which are syncing authenticated
|
* Helper class for syncing realms. The class should simplify the creation of realms, which are syncing authenticated
|
||||||
@@ -80,22 +85,107 @@ public final class SyncingRealmHelper {
|
|||||||
this.groupManager = groupManager;
|
this.groupManager = groupManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
/**
|
/**
|
||||||
* Create {@link AuthenticationInfo} from user and groups.
|
* Create {@link AuthenticationInfo} from user and groups.
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param realm name of the realm
|
|
||||||
* @param user authenticated user
|
|
||||||
* @param groups groups of the authenticated user
|
|
||||||
*
|
|
||||||
* @return authentication info
|
|
||||||
*/
|
*/
|
||||||
public AuthenticationInfo createAuthenticationInfo(String realm, User user,
|
public AuthenticationInfoBuilder.ForRealm authenticationInfo() {
|
||||||
String... groups) {
|
return new AuthenticationInfoBuilder().new ForRealm();
|
||||||
return createAuthenticationInfo(realm, user, ImmutableList.copyOf(groups));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AuthenticationInfoBuilder {
|
||||||
|
private String realm;
|
||||||
|
private User user;
|
||||||
|
private Collection<String> groups;
|
||||||
|
private boolean external;
|
||||||
|
|
||||||
|
private AuthenticationInfo build() {
|
||||||
|
return SyncingRealmHelper.this.createAuthenticationInfo(realm, user, groups, external);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ForRealm {
|
||||||
|
private ForRealm() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the realm.
|
||||||
|
* @param realm name of the realm
|
||||||
|
*/
|
||||||
|
public ForUser forRealm(String realm) {
|
||||||
|
AuthenticationInfoBuilder.this.realm = realm;
|
||||||
|
return AuthenticationInfoBuilder.this.new ForUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ForUser {
|
||||||
|
private ForUser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the user.
|
||||||
|
* @param user authenticated user
|
||||||
|
*/
|
||||||
|
public AuthenticationInfoBuilder.WithGroups andUser(User user) {
|
||||||
|
AuthenticationInfoBuilder.this.user = user;
|
||||||
|
return AuthenticationInfoBuilder.this.new WithGroups();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WithGroups {
|
||||||
|
private WithGroups() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the authentication info without groups.
|
||||||
|
* @return The complete {@link AuthenticationInfo}
|
||||||
|
*/
|
||||||
|
public AuthenticationInfo withoutGroups() {
|
||||||
|
return withGroups(emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the internal groups for the user.
|
||||||
|
* @param groups groups of the authenticated user
|
||||||
|
* @return The complete {@link AuthenticationInfo}
|
||||||
|
*/
|
||||||
|
public AuthenticationInfo withGroups(String... groups) {
|
||||||
|
return withGroups(asList(groups));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the internal groups for the user.
|
||||||
|
* @param groups groups of the authenticated user
|
||||||
|
* @return The complete {@link AuthenticationInfo}
|
||||||
|
*/
|
||||||
|
public AuthenticationInfo withGroups(Collection<String> groups) {
|
||||||
|
AuthenticationInfoBuilder.this.groups = groups;
|
||||||
|
AuthenticationInfoBuilder.this.external = false;
|
||||||
|
return build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the external groups for the user.
|
||||||
|
* @param groups external groups of the authenticated user
|
||||||
|
* @return The complete {@link AuthenticationInfo}
|
||||||
|
*/
|
||||||
|
public AuthenticationInfo withExternalGroups(String... groups) {
|
||||||
|
return withExternalGroups(asList(groups));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the external groups for the user.
|
||||||
|
* @param groups external groups of the authenticated user
|
||||||
|
* @return The complete {@link AuthenticationInfo}
|
||||||
|
*/
|
||||||
|
public AuthenticationInfo withExternalGroups(Collection<String> groups) {
|
||||||
|
AuthenticationInfoBuilder.this.groups = groups;
|
||||||
|
AuthenticationInfoBuilder.this.external = true;
|
||||||
|
return build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create {@link AuthenticationInfo} from user and groups.
|
* Create {@link AuthenticationInfo} from user and groups.
|
||||||
*
|
*
|
||||||
@@ -106,13 +196,13 @@ public final class SyncingRealmHelper {
|
|||||||
*
|
*
|
||||||
* @return authentication info
|
* @return authentication info
|
||||||
*/
|
*/
|
||||||
public AuthenticationInfo createAuthenticationInfo(String realm, User user,
|
private AuthenticationInfo createAuthenticationInfo(String realm, User user,
|
||||||
Collection<String> groups) {
|
Collection<String> groups, boolean externalGroups) {
|
||||||
SimplePrincipalCollection collection = new SimplePrincipalCollection();
|
SimplePrincipalCollection collection = new SimplePrincipalCollection();
|
||||||
|
|
||||||
collection.add(user.getId(), realm);
|
collection.add(user.getId(), realm);
|
||||||
collection.add(user, realm);
|
collection.add(user, realm);
|
||||||
collection.add(new GroupNames(groups), realm);
|
collection.add(new GroupNames(groups, externalGroups), realm);
|
||||||
|
|
||||||
return new SimpleAuthenticationInfo(collection, user.getPassword());
|
return new SimpleAuthenticationInfo(collection, user.getPassword());
|
||||||
}
|
}
|
||||||
@@ -163,4 +253,4 @@ public final class SyncingRealmHelper {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,25 @@
|
|||||||
|
|
||||||
package sonia.scm.store;
|
package sonia.scm.store;
|
||||||
|
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The BlobStoreFactory can be used to create new or get existing
|
* The BlobStoreFactory can be used to create a new or get an existing {@link BlobStore}s.
|
||||||
* {@link BlobStore}s.
|
* <br>
|
||||||
|
* You can either create a global {@link BlobStore} or a {@link BlobStore} for a specific repository. To create a global
|
||||||
|
* {@link BlobStore} call:
|
||||||
|
* <code><pre>
|
||||||
|
* blobStoreFactory
|
||||||
|
* .withName("name")
|
||||||
|
* .build();
|
||||||
|
* </pre></code>
|
||||||
|
* To create a {@link BlobStore} for a specific repository call:
|
||||||
|
* <code><pre>
|
||||||
|
* blobStoreFactory
|
||||||
|
* .withName("name")
|
||||||
|
* .forRepository(repository)
|
||||||
|
* .build();
|
||||||
|
* </pre></code>
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 1.23
|
* @since 1.23
|
||||||
@@ -45,13 +61,68 @@ package sonia.scm.store;
|
|||||||
public interface BlobStoreFactory {
|
public interface BlobStoreFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link BlobStore} with the given name, if the {@link BlobStore}
|
* Creates a new or gets an existing {@link BlobStore}. Instead of calling this method you should use the floating API
|
||||||
* with the given name does not exists the factory will create a new one.
|
* from {@link #withName(String)}.
|
||||||
*
|
*
|
||||||
*
|
* @param storeParameters The parameters for the blob store.
|
||||||
* @param name name of the {@link BlobStore}
|
* @return A new or an existing {@link BlobStore} for the given parameters.
|
||||||
*
|
|
||||||
* @return {@link BlobStore} with the given name
|
|
||||||
*/
|
*/
|
||||||
public BlobStore getBlobStore(String name);
|
BlobStore getStore(final StoreParameters storeParameters);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to create a new or get an existing {@link BlobStore} with a floating API.
|
||||||
|
* @param name The name for the {@link BlobStore}.
|
||||||
|
* @return Floating API to either specify a repository or directly build a global {@link BlobStore}.
|
||||||
|
*/
|
||||||
|
default FloatingStoreParameters.Builder withName(String name) {
|
||||||
|
return new FloatingStoreParameters(this).new Builder(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class FloatingStoreParameters implements StoreParameters {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private Repository repository;
|
||||||
|
|
||||||
|
private final BlobStoreFactory factory;
|
||||||
|
|
||||||
|
FloatingStoreParameters(BlobStoreFactory factory) {
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Repository getRepository() {
|
||||||
|
return repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Builder {
|
||||||
|
|
||||||
|
Builder(String name) {
|
||||||
|
FloatingStoreParameters.this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to create or get a {@link BlobStore} for a specific repository. This step is optional. If you want to
|
||||||
|
* have a global {@link BlobStore}, omit this.
|
||||||
|
* @param repository The optional repository for the {@link BlobStore}.
|
||||||
|
* @return Floating API to finish the call.
|
||||||
|
*/
|
||||||
|
public FloatingStoreParameters.Builder forRepository(Repository repository) {
|
||||||
|
FloatingStoreParameters.this.repository = repository;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates or gets the {@link BlobStore} with the given name and (if specified) the given repository. If no
|
||||||
|
* repository is given, the {@link BlobStore} will be global.
|
||||||
|
*/
|
||||||
|
public BlobStore build(){
|
||||||
|
return factory.getStore(FloatingStoreParameters.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,12 +32,30 @@
|
|||||||
|
|
||||||
package sonia.scm.store;
|
package sonia.scm.store;
|
||||||
|
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ConfigurationEntryStoreFactory can be used to create new or get existing
|
* The ConfigurationEntryStoreFactory can be used to create new or get existing {@link ConfigurationEntryStore}s.
|
||||||
* {@link ConfigurationEntryStore}s. <b>Note:</b> the default implementation
|
* <br>
|
||||||
* uses the same location as the {@link StoreFactory}, so be sure that the
|
* <b>Note:</b> the default implementation uses the same location as the {@link ConfigurationStoreFactory}, so be sure
|
||||||
* store names are unique for all {@link ConfigurationEntryStore}s and
|
* that the store names are unique for all {@link ConfigurationEntryStore}s and {@link ConfigurationEntryStore}s.
|
||||||
* {@link Store}s.
|
* <br>
|
||||||
|
* You can either create a global {@link ConfigurationEntryStore} or a {@link ConfigurationEntryStore} for a specific
|
||||||
|
* repository. To create a global {@link ConfigurationEntryStore} call:
|
||||||
|
* <code><pre>
|
||||||
|
* configurationEntryStoreFactory
|
||||||
|
* .withType(PersistedType.class)
|
||||||
|
* .withName("name")
|
||||||
|
* .build();
|
||||||
|
* </pre></code>
|
||||||
|
* To create a {@link ConfigurationEntryStore} for a specific repository call:
|
||||||
|
* <code><pre>
|
||||||
|
* configurationEntryStoreFactory
|
||||||
|
* .withType(PersistedType.class)
|
||||||
|
* .withName("name")
|
||||||
|
* .forRepository(repository)
|
||||||
|
* .build();
|
||||||
|
* </pre></code>
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
* @since 1.31
|
* @since 1.31
|
||||||
@@ -45,18 +63,73 @@ package sonia.scm.store;
|
|||||||
* @apiviz.landmark
|
* @apiviz.landmark
|
||||||
* @apiviz.uses sonia.scm.store.ConfigurationEntryStore
|
* @apiviz.uses sonia.scm.store.ConfigurationEntryStore
|
||||||
*/
|
*/
|
||||||
public interface ConfigurationEntryStoreFactory
|
public interface ConfigurationEntryStoreFactory {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an existing {@link ConfigurationEntryStore} or create a new one.
|
* Creates a new or gets an existing {@link ConfigurationEntryStore}. Instead of calling this method you should use
|
||||||
|
* the floating API from {@link #withType(Class)}.
|
||||||
*
|
*
|
||||||
*
|
* @param storeParameters The parameters for the {@link ConfigurationEntryStore}.
|
||||||
* @param type type of the store objects
|
* @return A new or an existing {@link ConfigurationEntryStore} for the given parameters.
|
||||||
* @param name name of the store
|
|
||||||
* @param <T> type of the store objects
|
|
||||||
*
|
|
||||||
* @return {@link ConfigurationEntryStore} with given name and type
|
|
||||||
*/
|
*/
|
||||||
public <T> ConfigurationEntryStore<T> getStore(Class<T> type, String name);
|
<T> ConfigurationEntryStore<T> getStore(final TypedStoreParameters<T> storeParameters);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to create a new or get an existing {@link ConfigurationEntryStore} with a floating API.
|
||||||
|
* @param type The type for the {@link ConfigurationEntryStore}.
|
||||||
|
* @return Floating API to set the name and either specify a repository or directly build a global
|
||||||
|
* {@link ConfigurationEntryStore}.
|
||||||
|
*/
|
||||||
|
default <T> TypedFloatingConfigurationEntryStoreParameters<T>.Builder withType(Class<T> type) {
|
||||||
|
return new TypedFloatingConfigurationEntryStoreParameters<T>(this).new Builder(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TypedFloatingConfigurationEntryStoreParameters<T> {
|
||||||
|
|
||||||
|
private final TypedStoreParametersImpl<T> parameters = new TypedStoreParametersImpl<>();
|
||||||
|
private final ConfigurationEntryStoreFactory factory;
|
||||||
|
|
||||||
|
TypedFloatingConfigurationEntryStoreParameters(ConfigurationEntryStoreFactory factory) {
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Builder {
|
||||||
|
|
||||||
|
Builder(Class<T> type) {
|
||||||
|
parameters.setType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to set the name for the {@link ConfigurationEntryStore}.
|
||||||
|
* @param name The name for the {@link ConfigurationEntryStore}.
|
||||||
|
* @return Floating API to either specify a repository or directly build a global {@link ConfigurationEntryStore}.
|
||||||
|
*/
|
||||||
|
public OptionalRepositoryBuilder withName(String name) {
|
||||||
|
parameters.setName(name);
|
||||||
|
return new OptionalRepositoryBuilder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OptionalRepositoryBuilder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to create or get a {@link ConfigurationEntryStore} for a specific repository. This step is optional. If
|
||||||
|
* you want to have a global {@link ConfigurationEntryStore}, omit this.
|
||||||
|
* @param repository The optional repository for the {@link ConfigurationEntryStore}.
|
||||||
|
* @return Floating API to finish the call.
|
||||||
|
*/
|
||||||
|
public OptionalRepositoryBuilder forRepository(Repository repository) {
|
||||||
|
parameters.setRepository(repository);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates or gets the {@link ConfigurationEntryStore} with the given name and (if specified) the given repository.
|
||||||
|
* If no repository is given, the {@link ConfigurationEntryStore} will be global.
|
||||||
|
*/
|
||||||
|
public ConfigurationEntryStore<T> build(){
|
||||||
|
return factory.getStore(parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,10 @@
|
|||||||
|
|
||||||
package sonia.scm.store;
|
package sonia.scm.store;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static java.util.Optional.ofNullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConfigurationStore for configuration objects. <strong>Note:</strong> the default
|
* ConfigurationStore for configuration objects. <strong>Note:</strong> the default
|
||||||
* implementation use JAXB to marshall the configuration objects.
|
* implementation use JAXB to marshall the configuration objects.
|
||||||
@@ -50,7 +54,17 @@ public interface ConfigurationStore<T>
|
|||||||
*
|
*
|
||||||
* @return configuration object from store
|
* @return configuration object from store
|
||||||
*/
|
*/
|
||||||
public T get();
|
T get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configuration object from store.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return configuration object from store
|
||||||
|
*/
|
||||||
|
default Optional<T> getOptional() {
|
||||||
|
return ofNullable(get());
|
||||||
|
}
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
//~--- set methods ----------------------------------------------------------
|
||||||
|
|
||||||
@@ -58,7 +72,7 @@ public interface ConfigurationStore<T>
|
|||||||
* Stores the given configuration object to the store.
|
* Stores the given configuration object to the store.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param obejct configuration object to store
|
* @param object configuration object to store
|
||||||
*/
|
*/
|
||||||
public void set(T obejct);
|
void set(T object);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user