mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-11 07:55:47 +01:00
merge with 2.0.0-m3
This commit is contained in:
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# ignore everything except scm-server.tar.gz
|
||||||
|
**
|
||||||
|
!scm-server/target/*.tar.gz
|
||||||
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM openjdk:8u171-alpine3.8
|
||||||
|
|
||||||
|
ENV SCM_HOME=/var/lib/scm
|
||||||
|
|
||||||
|
RUN set -x \
|
||||||
|
&& apk add --no-cache mercurial bash \
|
||||||
|
&& addgroup -S -g 1000 scm \
|
||||||
|
&& adduser -S -s /bin/false -G scm -h /opt/scm-server -D -H -u 1000 scm \
|
||||||
|
&& mkdir ${SCM_HOME} \
|
||||||
|
&& chown scm:scm ${SCM_HOME}
|
||||||
|
|
||||||
|
ADD scm-server/target/scm-server-app.tar.gz /opt
|
||||||
|
RUN chown -R scm:scm /opt/scm-server
|
||||||
|
|
||||||
|
WORKDIR /opt/scm-server
|
||||||
|
VOLUME [ "${SCM_HOME}", "/opt/scm-server/var/log" ]
|
||||||
|
EXPOSE 8080
|
||||||
|
USER scm
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/opt/scm-server/bin/scm-server" ]
|
||||||
37
Jenkinsfile
vendored
37
Jenkinsfile
vendored
@@ -4,14 +4,15 @@
|
|||||||
@Library('github.com/cloudogu/ces-build-lib@59d3e94')
|
@Library('github.com/cloudogu/ces-build-lib@59d3e94')
|
||||||
import com.cloudogu.ces.cesbuildlib.*
|
import com.cloudogu.ces.cesbuildlib.*
|
||||||
|
|
||||||
node() { // No specific label
|
node('docker') {
|
||||||
|
|
||||||
// Change this as when we go back to default - necessary for proper SonarQube analysis
|
// Change this as when we go back to default - necessary for proper SonarQube analysis
|
||||||
mainBranch = "2.0.0-m3"
|
mainBranch = "2.0.0-m3"
|
||||||
|
|
||||||
properties([
|
properties([
|
||||||
// Keep only the last 10 build to preserve space
|
// Keep only the last 10 build to preserve space
|
||||||
buildDiscarder(logRotator(numToKeepStr: '10'))
|
buildDiscarder(logRotator(numToKeepStr: '10')),
|
||||||
|
disableConcurrentBuilds()
|
||||||
])
|
])
|
||||||
|
|
||||||
timeout(activity: true, time: 20, unit: 'MINUTES') {
|
timeout(activity: true, time: 20, unit: 'MINUTES') {
|
||||||
@@ -44,6 +45,26 @@ node() { // No specific label
|
|||||||
currentBuild.result = 'UNSTABLE'
|
currentBuild.result = 'UNSTABLE'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def commitHash = getCommitHash()
|
||||||
|
def dockerImageTag = "2.0.0-dev-${commitHash.substring(0,7)}-${BUILD_NUMBER}"
|
||||||
|
|
||||||
|
if (isMainBranch()) {
|
||||||
|
stage('Docker') {
|
||||||
|
def image = docker.build('cloudogu/scm-manager')
|
||||||
|
docker.withRegistry('', 'hub.docker.com-cesmarvin') {
|
||||||
|
image.push(dockerImageTag)
|
||||||
|
image.push('latest')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Deployment') {
|
||||||
|
build job: 'scm-manager/next-scm.cloudogu.com', propagate: false, wait: false, parameters: [
|
||||||
|
string(name: 'changeset', value: commitHash),
|
||||||
|
string(name: 'imageTag', value: dockerImageTag)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Archive Unit and integration test results, if any
|
// Archive Unit and integration test results, if any
|
||||||
@@ -62,7 +83,7 @@ Maven setupMavenBuild() {
|
|||||||
// Keep this version number in sync with .mvn/maven-wrapper.properties
|
// Keep this version number in sync with .mvn/maven-wrapper.properties
|
||||||
Maven mvn = new MavenInDocker(this, "3.5.2-jdk-8")
|
Maven mvn = new MavenInDocker(this, "3.5.2-jdk-8")
|
||||||
|
|
||||||
if (mainBranch.equals(env.BRANCH_NAME)) {
|
if (isMainBranch()) {
|
||||||
// Release starts javadoc, which takes very long, so do only for certain branches
|
// Release starts javadoc, which takes very long, so do only for certain branches
|
||||||
mvn.additionalArgs += ' -DperformRelease'
|
mvn.additionalArgs += ' -DperformRelease'
|
||||||
// JDK8 is more strict, we should fix this before the next release. Right now, this is just not the focus, yet.
|
// JDK8 is more strict, we should fix this before the next release. Right now, this is just not the focus, yet.
|
||||||
@@ -89,7 +110,7 @@ void analyzeWith(Maven mvn) {
|
|||||||
"-Dsonar.pullrequest.bitbucketcloud.repository=scm-manager "
|
"-Dsonar.pullrequest.bitbucketcloud.repository=scm-manager "
|
||||||
} else {
|
} else {
|
||||||
mvnArgs += " -Dsonar.branch.name=${env.BRANCH_NAME} "
|
mvnArgs += " -Dsonar.branch.name=${env.BRANCH_NAME} "
|
||||||
if (!mainBranch.equals(env.BRANCH_NAME)) {
|
if (!isMainBranch()) {
|
||||||
// Avoid exception "The main branch must not have a target" on main branch
|
// Avoid exception "The main branch must not have a target" on main branch
|
||||||
mvnArgs += " -Dsonar.branch.target=${mainBranch} "
|
mvnArgs += " -Dsonar.branch.target=${mainBranch} "
|
||||||
}
|
}
|
||||||
@@ -98,6 +119,10 @@ void analyzeWith(Maven mvn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isMainBranch() {
|
||||||
|
return mainBranch.equals(env.BRANCH_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
boolean waitForQualityGateWebhookToBeCalled() {
|
boolean waitForQualityGateWebhookToBeCalled() {
|
||||||
boolean isQualityGateSucceeded = true
|
boolean isQualityGateSucceeded = true
|
||||||
timeout(time: 2, unit: 'MINUTES') { // Needed when there is no webhook for example
|
timeout(time: 2, unit: 'MINUTES') { // Needed when there is no webhook for example
|
||||||
@@ -114,6 +139,10 @@ String getCommitAuthorComplete() {
|
|||||||
new Sh(this).returnStdOut 'hg log --branch . --limit 1 --template "{author}"'
|
new Sh(this).returnStdOut 'hg log --branch . --limit 1 --template "{author}"'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getCommitHash() {
|
||||||
|
new Sh(this).returnStdOut 'hg log --branch . --limit 1 --template "{node}"'
|
||||||
|
}
|
||||||
|
|
||||||
String getCommitAuthorEmail() {
|
String getCommitAuthorEmail() {
|
||||||
def matcher = getCommitAuthorComplete() =~ "<(.*?)>"
|
def matcher = getCommitAuthorComplete() =~ "<(.*?)>"
|
||||||
matcher ? matcher[0][1] : ""
|
matcher ? matcher[0][1] : ""
|
||||||
|
|||||||
21
deployments/helm/.helmignore
Normal file
21
deployments/helm/.helmignore
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
5
deployments/helm/Chart.yaml
Normal file
5
deployments/helm/Chart.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
appVersion: "1.0"
|
||||||
|
description: A Helm chart for SCM-Manager
|
||||||
|
name: scm-manager
|
||||||
|
version: 0.1.0
|
||||||
19
deployments/helm/templates/NOTES.txt
Normal file
19
deployments/helm/templates/NOTES.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
1. Get the application URL by running these commands:
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if contains "NodePort" .Values.service.type }}
|
||||||
|
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "scm-manager.fullname" . }})
|
||||||
|
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||||
|
echo http://$NODE_IP:$NODE_PORT
|
||||||
|
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||||
|
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||||
|
You can watch the status of by running 'kubectl get svc -w {{ include "scm-manager.fullname" . }}'
|
||||||
|
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "scm-manager.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
||||||
|
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||||
|
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||||
|
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ include "scm-manager.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||||
|
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||||
|
kubectl port-forward $POD_NAME 8080:80
|
||||||
|
{{- end }}
|
||||||
32
deployments/helm/templates/_helpers.tpl
Normal file
32
deployments/helm/templates/_helpers.tpl
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{{/* vim: set filetype=mustache: */}}
|
||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "scm-manager.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "scm-manager.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride -}}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||||
|
{{- if contains $name .Release.Name -}}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "scm-manager.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
160
deployments/helm/templates/configmap.yaml
Normal file
160
deployments/helm/templates/configmap.yaml
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "scm-manager.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "scm-manager.name" . }}
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
release: "{{ .Release.Name }}"
|
||||||
|
heritage: "{{ .Release.Service }}"
|
||||||
|
data:
|
||||||
|
server-config.xml: |
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
|
||||||
|
<Configure id="ScmServer" class="org.eclipse.jetty.server.Server">
|
||||||
|
|
||||||
|
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
|
||||||
|
<!-- increase header size for mercurial -->
|
||||||
|
<Set name="requestHeaderSize">16384</Set>
|
||||||
|
<Set name="responseHeaderSize">16384</Set>
|
||||||
|
|
||||||
|
{{- if .Values.ingress.enabled -}}
|
||||||
|
<!--
|
||||||
|
We have to enable ForwardedRequestCustomizer in order to understand X-Forwarded-xxx headers.
|
||||||
|
Without the ForwardedRequestCustomizer, scm will possibly generate wrong links
|
||||||
|
-->
|
||||||
|
<Call name="addCustomizer">
|
||||||
|
<Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
|
||||||
|
</Call>
|
||||||
|
{{- end }}
|
||||||
|
</New>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Connectors
|
||||||
|
-->
|
||||||
|
<Call name="addConnector">
|
||||||
|
<Arg>
|
||||||
|
<New class="org.eclipse.jetty.server.ServerConnector">
|
||||||
|
<Arg name="server">
|
||||||
|
<Ref refid="ScmServer" />
|
||||||
|
</Arg>
|
||||||
|
<Arg name="factories">
|
||||||
|
<Array type="org.eclipse.jetty.server.ConnectionFactory">
|
||||||
|
<Item>
|
||||||
|
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
|
||||||
|
<Arg name="config">
|
||||||
|
<Ref refid="httpConfig" />
|
||||||
|
</Arg>
|
||||||
|
</New>
|
||||||
|
</Item>
|
||||||
|
</Array>
|
||||||
|
</Arg>
|
||||||
|
<Set name="port">
|
||||||
|
<SystemProperty name="jetty.port" default="8080" />
|
||||||
|
</Set>
|
||||||
|
</New>
|
||||||
|
</Arg>
|
||||||
|
</Call>
|
||||||
|
|
||||||
|
<New id="scm-webapp" class="org.eclipse.jetty.webapp.WebAppContext">
|
||||||
|
<Set name="contextPath">/scm</Set>
|
||||||
|
<Set name="war">
|
||||||
|
<SystemProperty name="basedir" default="."/>/var/webapp/scm-webapp.war</Set>
|
||||||
|
<!-- disable directory listings -->
|
||||||
|
<Call name="setInitParameter">
|
||||||
|
<Arg>org.eclipse.jetty.servlet.Default.dirAllowed</Arg>
|
||||||
|
<Arg>false</Arg>
|
||||||
|
</Call>
|
||||||
|
<Set name="tempDirectory">
|
||||||
|
<SystemProperty name="basedir" default="."/>/work/scm
|
||||||
|
</Set>
|
||||||
|
</New>
|
||||||
|
|
||||||
|
<New id="docroot" class="org.eclipse.jetty.webapp.WebAppContext">
|
||||||
|
<Set name="contextPath">/</Set>
|
||||||
|
<Set name="baseResource">
|
||||||
|
<New class="org.eclipse.jetty.util.resource.ResourceCollection">
|
||||||
|
<Arg>
|
||||||
|
<Array type="java.lang.String">
|
||||||
|
<Item>
|
||||||
|
<SystemProperty name="basedir" default="."/>/var/webapp/docroot</Item>
|
||||||
|
</Array>
|
||||||
|
</Arg>
|
||||||
|
</New>
|
||||||
|
</Set>
|
||||||
|
<Set name="tempDirectory">
|
||||||
|
<SystemProperty name="basedir" default="."/>/work/docroot
|
||||||
|
</Set>
|
||||||
|
</New>
|
||||||
|
|
||||||
|
<Set name="handler">
|
||||||
|
<New class="org.eclipse.jetty.server.handler.HandlerCollection">
|
||||||
|
<Set name="handlers">
|
||||||
|
<Array type="org.eclipse.jetty.server.Handler">
|
||||||
|
<Item>
|
||||||
|
<Ref id="scm-webapp" />
|
||||||
|
</Item>
|
||||||
|
<Item>
|
||||||
|
<Ref id="docroot" />
|
||||||
|
</Item>
|
||||||
|
</Array>
|
||||||
|
</Set>
|
||||||
|
</New>
|
||||||
|
</Set>
|
||||||
|
|
||||||
|
</Configure>
|
||||||
|
|
||||||
|
logging.xml: |
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<--
|
||||||
|
in a container environment we only need stdout
|
||||||
|
-->
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="sonia.scm" level="INFO" />
|
||||||
|
|
||||||
|
<!-- suppress massive gzip logging -->
|
||||||
|
<logger name="sonia.scm.filter.GZipFilter" level="WARN" />
|
||||||
|
<logger name="sonia.scm.filter.GZipResponseStream" level="WARN" />
|
||||||
|
|
||||||
|
<logger name="sonia.scm.util.ServiceUtil" level="WARN" />
|
||||||
|
|
||||||
|
<!-- event bus -->
|
||||||
|
<logger name="sonia.scm.event.LegmanScmEventBus" level="INFO" />
|
||||||
|
|
||||||
|
<!-- shiro -->
|
||||||
|
<!--
|
||||||
|
<logger name="org.apache.shiro" level="INFO" />
|
||||||
|
<logger name="org.apache.shiro.authc.pam.ModularRealmAuthenticator" level="DEBUG" />
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- svnkit -->
|
||||||
|
<!--
|
||||||
|
<logger name="svnkit" level="WARN" />
|
||||||
|
<logger name="svnkit.network" level="DEBUG" />
|
||||||
|
<logger name="svnkit.fsfs" level="WARN" />
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- javahg -->
|
||||||
|
<!--
|
||||||
|
<logger name="com.aragost.javahg" level="DEBUG" />
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- ehcache -->
|
||||||
|
<!--
|
||||||
|
<logger name="net.sf.ehcache" level="DEBUG" />
|
||||||
|
-->
|
||||||
|
|
||||||
|
<root level="WARN">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
77
deployments/helm/templates/deployment.yaml
Normal file
77
deployments/helm/templates/deployment.yaml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
apiVersion: apps/v1beta2
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "scm-manager.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "scm-manager.name" . }}
|
||||||
|
chart: {{ include "scm-manager.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
replicas: 1 # could not be scaled
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ include "scm-manager.name" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ include "scm-manager.name" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
spec:
|
||||||
|
initContainers:
|
||||||
|
- name: volume-permissions
|
||||||
|
image: alpine:3.8
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ['sh', '-c', 'chown 1000:1000 /data']
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /scm
|
||||||
|
port: http
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /scm
|
||||||
|
port: http
|
||||||
|
resources:
|
||||||
|
{{ toYaml .Values.resources | indent 12 }}
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/scm
|
||||||
|
- name: config
|
||||||
|
mountPath: /opt/scm-server/conf
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
{{- if .Values.persistence.enabled }}
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ include "scm-manager.fullname" . }}
|
||||||
|
{{- else }}
|
||||||
|
emptyDir: {}
|
||||||
|
{{- end }}
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: {{ include "scm-manager.fullname" . }}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
38
deployments/helm/templates/ingress.yaml
Normal file
38
deployments/helm/templates/ingress.yaml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{{- if .Values.ingress.enabled -}}
|
||||||
|
{{- $fullName := include "scm-manager.fullname" . -}}
|
||||||
|
{{- $ingressPath := .Values.ingress.path -}}
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "scm-manager.name" . }}
|
||||||
|
chart: {{ include "scm-manager.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{ toYaml . | indent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- if .Values.ingress.tls }}
|
||||||
|
tls:
|
||||||
|
{{- range .Values.ingress.tls }}
|
||||||
|
- hosts:
|
||||||
|
{{- range .hosts }}
|
||||||
|
- {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
secretName: {{ .secretName }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- host: {{ . | quote }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: {{ $ingressPath }}
|
||||||
|
backend:
|
||||||
|
serviceName: {{ $fullName }}
|
||||||
|
servicePort: http
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
24
deployments/helm/templates/pvc.yaml
Normal file
24
deployments/helm/templates/pvc.yaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{{- if .Values.persistence.enabled -}}
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: {{ include "scm-manager.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "scm-manager.name" . }}
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
release: "{{ .Release.Name }}"
|
||||||
|
heritage: "{{ .Release.Service }}"
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.accessMode | quote }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.size | quote }}
|
||||||
|
{{- if .Values.persistence.storageClass }}
|
||||||
|
{{- if (eq "-" .Values.persistence.storageClass) }}
|
||||||
|
storageClassName: ""
|
||||||
|
{{- else }}
|
||||||
|
storageClassName: "{{ .Values.persistence.storageClass }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
19
deployments/helm/templates/service.yaml
Normal file
19
deployments/helm/templates/service.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "scm-manager.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "scm-manager.name" . }}
|
||||||
|
chart: {{ include "scm-manager.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.port }}
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: {{ include "scm-manager.name" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
65
deployments/helm/values.yaml
Normal file
65
deployments/helm/values.yaml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Default values for scm-manager.
|
||||||
|
# This is a YAML-formatted file.
|
||||||
|
# Declare variables to be passed into your templates.
|
||||||
|
|
||||||
|
# replicaCount: 1
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: cloudogu/scm-manager
|
||||||
|
# TODO change after release, to something more stable
|
||||||
|
tag: latest
|
||||||
|
pullPolicy: Always
|
||||||
|
|
||||||
|
nameOverride: ""
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: LoadBalancer
|
||||||
|
port: 80
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: false
|
||||||
|
annotations: {}
|
||||||
|
# kubernetes.io/ingress.class: nginx
|
||||||
|
# kubernetes.io/tls-acme: "true"
|
||||||
|
path: /
|
||||||
|
hosts:
|
||||||
|
- scm-manager.local
|
||||||
|
tls: []
|
||||||
|
# - secretName: scm-manager-tls
|
||||||
|
# hosts:
|
||||||
|
# - scm-manager.local
|
||||||
|
|
||||||
|
## Enable persistence using Persistent Volume Claims
|
||||||
|
## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/
|
||||||
|
##
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
## ghost data Persistent Volume Storage Class
|
||||||
|
## If defined, storageClassName: <storageClass>
|
||||||
|
## If set to "-", storageClassName: "", which disables dynamic provisioning
|
||||||
|
## If undefined (the default) or set to null, no storageClassName spec is
|
||||||
|
## set, choosing the default provisioner. (gp2 on AWS, standard on
|
||||||
|
## GKE, AWS & OpenStack)
|
||||||
|
##
|
||||||
|
# storageClass: "-"
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
size: 12Gi
|
||||||
|
|
||||||
|
resources:
|
||||||
|
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||||
|
# choice for the user. This also increases chances charts run on environments with little
|
||||||
|
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||||
|
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||||
|
limits:
|
||||||
|
cpu: 2000m
|
||||||
|
memory: 2048Mi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 256Mi
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
||||||
@@ -7,9 +7,4 @@ public class NotFoundException extends Exception {
|
|||||||
|
|
||||||
public NotFoundException() {
|
public NotFoundException() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public NotFoundException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package sonia.scm.user;
|
||||||
|
|
||||||
|
public class ChangePasswordNotAllowedException extends RuntimeException {
|
||||||
|
|
||||||
|
public static final String WRONG_USER_TYPE = "User of type {0} are not allowed to change password";
|
||||||
|
|
||||||
|
public ChangePasswordNotAllowedException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package sonia.scm.user;
|
||||||
|
|
||||||
|
public class InvalidPasswordException extends RuntimeException {
|
||||||
|
|
||||||
|
public static final String INVALID_MATCHING = "The given Password does not match with the stored one.";
|
||||||
|
|
||||||
|
public InvalidPasswordException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -274,6 +274,10 @@ User extends BasicPropertiesAware implements Principal, ModelObject, PermissionO
|
|||||||
//J+
|
//J+
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public User changePassword(String password){
|
||||||
|
setPassword(password);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ package sonia.scm.user;
|
|||||||
import sonia.scm.Manager;
|
import sonia.scm.Manager;
|
||||||
import sonia.scm.search.Searchable;
|
import sonia.scm.search.Searchable;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static sonia.scm.user.ChangePasswordNotAllowedException.WRONG_USER_TYPE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The central class for managing {@link User} objects.
|
* The central class for managing {@link User} objects.
|
||||||
* This class is a singleton and is available via injection.
|
* This class is a singleton and is available via injection.
|
||||||
@@ -68,4 +73,22 @@ public interface UserManager
|
|||||||
* @since 1.14
|
* @since 1.14
|
||||||
*/
|
*/
|
||||||
public String getDefaultType();
|
public String getDefaultType();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only account of the default type "xml" can change their password
|
||||||
|
*/
|
||||||
|
default Consumer<User> getUserTypeChecker() {
|
||||||
|
return user -> {
|
||||||
|
if (!isTypeDefault(user)) {
|
||||||
|
throw new ChangePasswordNotAllowedException(MessageFormat.format(WRONG_USER_TYPE, user.getType()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isTypeDefault(User user) {
|
||||||
|
return getDefaultType().equals(user.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class VndMediaType {
|
|||||||
public static final String PERMISSION = PREFIX + "permission" + SUFFIX;
|
public static final String PERMISSION = PREFIX + "permission" + SUFFIX;
|
||||||
public static final String CHANGESET = PREFIX + "changeset" + SUFFIX;
|
public static final String CHANGESET = PREFIX + "changeset" + SUFFIX;
|
||||||
public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX;
|
public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX;
|
||||||
public static final String MODIFICATIONS = PREFIX + "modifications" + SUFFIX;;
|
public static final String MODIFICATIONS = PREFIX + "modifications" + SUFFIX;
|
||||||
public static final String TAG = PREFIX + "tag" + SUFFIX;
|
public static final String TAG = PREFIX + "tag" + SUFFIX;
|
||||||
public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX;
|
public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX;
|
||||||
public static final String BRANCH = PREFIX + "branch" + SUFFIX;
|
public static final String BRANCH = PREFIX + "branch" + SUFFIX;
|
||||||
@@ -35,6 +35,8 @@ public class VndMediaType {
|
|||||||
public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX;
|
public static final String REPOSITORY_TYPE = PREFIX + "repositoryType" + SUFFIX;
|
||||||
public static final String UI_PLUGIN = PREFIX + "uiPlugin" + SUFFIX;
|
public static final String UI_PLUGIN = PREFIX + "uiPlugin" + SUFFIX;
|
||||||
public static final String UI_PLUGIN_COLLECTION = PREFIX + "uiPluginCollection" + SUFFIX;
|
public static final String UI_PLUGIN_COLLECTION = PREFIX + "uiPluginCollection" + SUFFIX;
|
||||||
|
@SuppressWarnings("squid:S2068")
|
||||||
|
public static final String PASSWORD_CHANGE = PREFIX + "passwordChange" + SUFFIX;
|
||||||
|
|
||||||
public static final String ME = PREFIX + "me" + SUFFIX;
|
public static final String ME = PREFIX + "me" + SUFFIX;
|
||||||
public static final String SOURCE = PREFIX + "source" + SUFFIX;
|
public static final String SOURCE = PREFIX + "source" + SUFFIX;
|
||||||
|
|||||||
79
scm-it/src/test/java/sonia/scm/it/MeITCase.java
Normal file
79
scm-it/src/test/java/sonia/scm/it/MeITCase.java
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package sonia.scm.it;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.it.utils.ScmRequests;
|
||||||
|
import sonia.scm.it.utils.TestData;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class MeITCase {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
TestData.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void adminShouldChangeOwnPassword() {
|
||||||
|
String newPassword = TestData.USER_SCM_ADMIN + "1";
|
||||||
|
// admin change the own password
|
||||||
|
ScmRequests.start()
|
||||||
|
.given()
|
||||||
|
.url(TestData.getMeUrl())
|
||||||
|
.usernameAndPassword(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN)
|
||||||
|
.getMeResource()
|
||||||
|
.assertStatusCode(200)
|
||||||
|
.usingMeResponse()
|
||||||
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
||||||
|
.assertPassword(Assert::assertNull)
|
||||||
|
.assertType(s -> assertThat(s).isEqualTo("xml"))
|
||||||
|
.requestChangePassword(TestData.USER_SCM_ADMIN, newPassword)
|
||||||
|
.assertStatusCode(204);
|
||||||
|
// assert password is changed -> login with the new Password than undo changes
|
||||||
|
ScmRequests.start()
|
||||||
|
.given()
|
||||||
|
.url(TestData.getUserUrl(TestData.USER_SCM_ADMIN))
|
||||||
|
.usernameAndPassword(TestData.USER_SCM_ADMIN, newPassword)
|
||||||
|
.getMeResource()
|
||||||
|
.assertStatusCode(200)
|
||||||
|
.usingMeResponse()
|
||||||
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))// still admin
|
||||||
|
.requestChangePassword(newPassword, TestData.USER_SCM_ADMIN)
|
||||||
|
.assertStatusCode(204);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldHidePasswordLinkIfUserTypeIsNotXML() {
|
||||||
|
String newUser = "user";
|
||||||
|
String password = "pass";
|
||||||
|
String type = "not XML Type";
|
||||||
|
TestData.createUser(newUser, password, true, type);
|
||||||
|
ScmRequests.start()
|
||||||
|
.given()
|
||||||
|
.url(TestData.getMeUrl())
|
||||||
|
.usernameAndPassword(newUser, password)
|
||||||
|
.getMeResource()
|
||||||
|
.assertStatusCode(200)
|
||||||
|
.usingMeResponse()
|
||||||
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
||||||
|
.assertPassword(Assert::assertNull)
|
||||||
|
.assertType(s -> assertThat(s).isEqualTo(type))
|
||||||
|
.assertPasswordLinkDoesNotExists();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGet403IfUserIsNotAdmin() {
|
||||||
|
String newUser = "user";
|
||||||
|
String password = "pass";
|
||||||
|
String type = "xml";
|
||||||
|
TestData.createUser(newUser, password, false, type);
|
||||||
|
ScmRequests.start()
|
||||||
|
.given()
|
||||||
|
.url(TestData.getMeUrl())
|
||||||
|
.usernameAndPassword(newUser, password)
|
||||||
|
.getMeResource()
|
||||||
|
.assertStatusCode(403);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,8 @@ import org.junit.rules.TemporaryFolder;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.Parameterized;
|
import org.junit.runners.Parameterized;
|
||||||
import org.junit.runners.Parameterized.Parameters;
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
import sonia.scm.it.utils.RepositoryUtil;
|
||||||
|
import sonia.scm.it.utils.TestData;
|
||||||
import sonia.scm.repository.PermissionType;
|
import sonia.scm.repository.PermissionType;
|
||||||
import sonia.scm.repository.client.api.RepositoryClient;
|
import sonia.scm.repository.client.api.RepositoryClient;
|
||||||
import sonia.scm.repository.client.api.RepositoryClientException;
|
import sonia.scm.repository.client.api.RepositoryClientException;
|
||||||
@@ -51,11 +53,11 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static sonia.scm.it.RepositoryUtil.addAndCommitRandomFile;
|
import static sonia.scm.it.utils.RepositoryUtil.addAndCommitRandomFile;
|
||||||
import static sonia.scm.it.RestUtil.given;
|
import static sonia.scm.it.utils.RestUtil.given;
|
||||||
import static sonia.scm.it.ScmTypes.availableScmTypes;
|
import static sonia.scm.it.utils.ScmTypes.availableScmTypes;
|
||||||
import static sonia.scm.it.TestData.USER_SCM_ADMIN;
|
import static sonia.scm.it.utils.TestData.USER_SCM_ADMIN;
|
||||||
import static sonia.scm.it.TestData.callRepository;
|
import static sonia.scm.it.utils.TestData.callRepository;
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public class PermissionsITCase {
|
public class PermissionsITCase {
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ import org.junit.rules.TemporaryFolder;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.Parameterized;
|
import org.junit.runners.Parameterized;
|
||||||
import org.junit.runners.Parameterized.Parameters;
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
import sonia.scm.it.utils.RepositoryUtil;
|
||||||
|
import sonia.scm.it.utils.TestData;
|
||||||
import sonia.scm.repository.client.api.RepositoryClient;
|
import sonia.scm.repository.client.api.RepositoryClient;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
@@ -53,11 +55,11 @@ import static org.hamcrest.Matchers.equalTo;
|
|||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static sonia.scm.it.RegExMatcher.matchesPattern;
|
import static sonia.scm.it.utils.RegExMatcher.matchesPattern;
|
||||||
import static sonia.scm.it.RestUtil.createResourceUrl;
|
import static sonia.scm.it.utils.RestUtil.createResourceUrl;
|
||||||
import static sonia.scm.it.RestUtil.given;
|
import static sonia.scm.it.utils.RestUtil.given;
|
||||||
import static sonia.scm.it.ScmTypes.availableScmTypes;
|
import static sonia.scm.it.utils.ScmTypes.availableScmTypes;
|
||||||
import static sonia.scm.it.TestData.repositoryJson;
|
import static sonia.scm.it.utils.TestData.repositoryJson;
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public class RepositoriesITCase {
|
public class RepositoriesITCase {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import io.restassured.response.ExtractableResponse;
|
|||||||
import io.restassured.response.Response;
|
import io.restassured.response.Response;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.assertj.core.util.Lists;
|
import org.assertj.core.util.Lists;
|
||||||
import org.assertj.core.util.Maps;
|
|
||||||
import org.junit.Assume;
|
import org.junit.Assume;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
@@ -12,6 +11,9 @@ import org.junit.Test;
|
|||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.Parameterized;
|
import org.junit.runners.Parameterized;
|
||||||
|
import sonia.scm.it.utils.RepositoryUtil;
|
||||||
|
import sonia.scm.it.utils.ScmRequests;
|
||||||
|
import sonia.scm.it.utils.TestData;
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
import sonia.scm.repository.client.api.ClientCommand;
|
import sonia.scm.repository.client.api.ClientCommand;
|
||||||
import sonia.scm.repository.client.api.RepositoryClient;
|
import sonia.scm.repository.client.api.RepositoryClient;
|
||||||
@@ -29,10 +31,10 @@ import static java.lang.Thread.sleep;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static sonia.scm.it.RestUtil.ADMIN_PASSWORD;
|
import static sonia.scm.it.utils.RestUtil.ADMIN_PASSWORD;
|
||||||
import static sonia.scm.it.RestUtil.ADMIN_USERNAME;
|
import static sonia.scm.it.utils.RestUtil.ADMIN_USERNAME;
|
||||||
import static sonia.scm.it.RestUtil.given;
|
import static sonia.scm.it.utils.RestUtil.given;
|
||||||
import static sonia.scm.it.ScmTypes.availableScmTypes;
|
import static sonia.scm.it.utils.ScmTypes.availableScmTypes;
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public class RepositoryAccessITCase {
|
public class RepositoryAccessITCase {
|
||||||
@@ -42,7 +44,7 @@ public class RepositoryAccessITCase {
|
|||||||
|
|
||||||
private final String repositoryType;
|
private final String repositoryType;
|
||||||
private File folder;
|
private File folder;
|
||||||
private RepositoryRequests.AppliedRepositoryGetRequest repositoryGetRequest;
|
private ScmRequests.AppliedRepositoryRequest repositoryGetRequest;
|
||||||
|
|
||||||
public RepositoryAccessITCase(String repositoryType) {
|
public RepositoryAccessITCase(String repositoryType) {
|
||||||
this.repositoryType = repositoryType;
|
this.repositoryType = repositoryType;
|
||||||
@@ -57,12 +59,17 @@ public class RepositoryAccessITCase {
|
|||||||
public void init() {
|
public void init() {
|
||||||
TestData.createDefault();
|
TestData.createDefault();
|
||||||
folder = tempFolder.getRoot();
|
folder = tempFolder.getRoot();
|
||||||
repositoryGetRequest = RepositoryRequests.start()
|
repositoryGetRequest = ScmRequests.start()
|
||||||
.given()
|
.given()
|
||||||
.url(TestData.getDefaultRepositoryUrl(repositoryType))
|
.url(TestData.getDefaultRepositoryUrl(repositoryType))
|
||||||
.usernameAndPassword(ADMIN_USERNAME, ADMIN_PASSWORD)
|
.usernameAndPassword(ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||||
.get()
|
.getRepositoryResource()
|
||||||
.assertStatusCode(HttpStatus.SC_OK);
|
.assertStatusCode(HttpStatus.SC_OK);
|
||||||
|
ScmRequests.AppliedMeRequest meGetRequest = ScmRequests.start()
|
||||||
|
.given()
|
||||||
|
.url(TestData.getMeUrl())
|
||||||
|
.usernameAndPassword(ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||||
|
.getMeResource();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -165,7 +172,7 @@ public class RepositoryAccessITCase {
|
|||||||
.isNotNull()
|
.isNotNull()
|
||||||
.contains(String.format("%s/sources/%s", repositoryUrl, changeset.getId()));
|
.contains(String.format("%s/sources/%s", repositoryUrl, changeset.getId()));
|
||||||
|
|
||||||
assertThat(response.body().jsonPath().getString("_embedded.tags.find{it.name=='" + tagName + "'}._links.changesets.href"))
|
assertThat(response.body().jsonPath().getString("_embedded.tags.find{it.name=='" + tagName + "'}._links.changeset.href"))
|
||||||
.as("assert single tag changesets link")
|
.as("assert single tag changesets link")
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.contains(String.format("%s/changesets/%s", repositoryUrl, changeset.getId()));
|
.contains(String.format("%s/changesets/%s", repositoryUrl, changeset.getId()));
|
||||||
|
|||||||
@@ -1,293 +0,0 @@
|
|||||||
package sonia.scm.it;
|
|
||||||
|
|
||||||
import io.restassured.RestAssured;
|
|
||||||
import io.restassured.response.Response;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encapsulate rest requests of a repository in builder pattern
|
|
||||||
* <p>
|
|
||||||
* A Get Request can be applied with the methods request*()
|
|
||||||
* These methods return a AppliedGet*Request object
|
|
||||||
* This object can be used to apply general assertions over the rest Assured response
|
|
||||||
* In the AppliedGet*Request classes there is a using*Response() method
|
|
||||||
* that return the *Response class containing specific operations related to the specific response
|
|
||||||
* the *Response class contains also the request*() method to apply the next GET request from a link in the response.
|
|
||||||
*/
|
|
||||||
public class RepositoryRequests {
|
|
||||||
|
|
||||||
private String url;
|
|
||||||
private String username;
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
static RepositoryRequests start() {
|
|
||||||
return new RepositoryRequests();
|
|
||||||
}
|
|
||||||
|
|
||||||
Given given() {
|
|
||||||
return new Given();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply a GET Request to the extracted url from the given link
|
|
||||||
*
|
|
||||||
* @param linkPropertyName the property name of link
|
|
||||||
* @param response the response containing the link
|
|
||||||
* @return the response of the GET request using the given link
|
|
||||||
*/
|
|
||||||
private Response getResponseFromLink(Response response, String linkPropertyName) {
|
|
||||||
return getResponse(response
|
|
||||||
.then()
|
|
||||||
.extract()
|
|
||||||
.path(linkPropertyName));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply a GET Request to the given <code>url</code> and return the response.
|
|
||||||
*
|
|
||||||
* @param url the url of the GET request
|
|
||||||
* @return the response of the GET request using the given <code>url</code>
|
|
||||||
*/
|
|
||||||
private Response getResponse(String url) {
|
|
||||||
return RestAssured.given()
|
|
||||||
.auth().preemptive().basic(username, password)
|
|
||||||
.when()
|
|
||||||
.get(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUrl(String url) {
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUsername(String username) {
|
|
||||||
this.username = username;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPassword(String password) {
|
|
||||||
this.password = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getPassword() {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Given {
|
|
||||||
|
|
||||||
GivenUrl url(String url) {
|
|
||||||
setUrl(url);
|
|
||||||
return new GivenUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class GivenWithUrlAndAuth {
|
|
||||||
AppliedRepositoryGetRequest get() {
|
|
||||||
return new AppliedRepositoryGetRequest(
|
|
||||||
getResponse(url)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppliedGetRequest<SELF extends AppliedGetRequest> {
|
|
||||||
private Response response;
|
|
||||||
|
|
||||||
public AppliedGetRequest(Response response) {
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* apply custom assertions to the actual response
|
|
||||||
*
|
|
||||||
* @param consumer consume the response in order to assert the content. the header, the payload etc..
|
|
||||||
* @return the self object
|
|
||||||
*/
|
|
||||||
SELF assertResponse(Consumer<Response> consumer) {
|
|
||||||
consumer.accept(response);
|
|
||||||
return (SELF) this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* special assertion of the status code
|
|
||||||
*
|
|
||||||
* @param expectedStatusCode the expected status code
|
|
||||||
* @return the self object
|
|
||||||
*/
|
|
||||||
SELF assertStatusCode(int expectedStatusCode) {
|
|
||||||
this.response.then().assertThat().statusCode(expectedStatusCode);
|
|
||||||
return (SELF) this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppliedRepositoryGetRequest extends AppliedGetRequest<AppliedRepositoryGetRequest> {
|
|
||||||
|
|
||||||
AppliedRepositoryGetRequest(Response response) {
|
|
||||||
super(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
RepositoryResponse usingRepositoryResponse() {
|
|
||||||
return new RepositoryResponse(super.response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RepositoryResponse {
|
|
||||||
|
|
||||||
private Response repositoryResponse;
|
|
||||||
|
|
||||||
public RepositoryResponse(Response repositoryResponse) {
|
|
||||||
this.repositoryResponse = repositoryResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppliedGetSourcesRequest requestSources() {
|
|
||||||
return new AppliedGetSourcesRequest(getResponseFromLink(repositoryResponse, "_links.sources.href"));
|
|
||||||
}
|
|
||||||
|
|
||||||
AppliedGetChangesetsRequest requestChangesets() {
|
|
||||||
return new AppliedGetChangesetsRequest(getResponseFromLink(repositoryResponse, "_links.changesets.href"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppliedGetChangesetsRequest extends AppliedGetRequest<AppliedGetChangesetsRequest> {
|
|
||||||
|
|
||||||
AppliedGetChangesetsRequest(Response response) {
|
|
||||||
super(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
ChangesetsResponse usingChangesetsResponse() {
|
|
||||||
return new ChangesetsResponse(super.response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChangesetsResponse {
|
|
||||||
private Response changesetsResponse;
|
|
||||||
|
|
||||||
public ChangesetsResponse(Response changesetsResponse) {
|
|
||||||
this.changesetsResponse = changesetsResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChangesetsResponse assertChangesets(Consumer<List<Map>> changesetsConsumer) {
|
|
||||||
List<Map> changesets = changesetsResponse.then().extract().path("_embedded.changesets");
|
|
||||||
changesetsConsumer.accept(changesets);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppliedGetDiffRequest requestDiff(String revision) {
|
|
||||||
return new AppliedGetDiffRequest(getResponseFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AppliedGetModificationsRequest requestModifications(String revision) {
|
|
||||||
return new AppliedGetModificationsRequest(getResponseFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.modifications.href"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppliedGetSourcesRequest extends AppliedGetRequest<AppliedGetSourcesRequest> {
|
|
||||||
|
|
||||||
public AppliedGetSourcesRequest(Response sourcesResponse) {
|
|
||||||
super(sourcesResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
SourcesResponse usingSourcesResponse() {
|
|
||||||
return new SourcesResponse(super.response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SourcesResponse {
|
|
||||||
|
|
||||||
private Response sourcesResponse;
|
|
||||||
|
|
||||||
SourcesResponse(Response sourcesResponse) {
|
|
||||||
this.sourcesResponse = sourcesResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
SourcesResponse assertRevision(Consumer<String> assertRevision) {
|
|
||||||
String revision = sourcesResponse.then().extract().path("revision");
|
|
||||||
assertRevision.accept(revision);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
SourcesResponse assertFiles(Consumer<List> assertFiles) {
|
|
||||||
List files = sourcesResponse.then().extract().path("files");
|
|
||||||
assertFiles.accept(files);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppliedGetChangesetsRequest requestFileHistory(String fileName) {
|
|
||||||
return new AppliedGetChangesetsRequest(getResponseFromLink(sourcesResponse, "_embedded.files.find{it.name=='" + fileName + "'}._links.history.href"));
|
|
||||||
}
|
|
||||||
|
|
||||||
AppliedGetSourcesRequest requestSelf(String fileName) {
|
|
||||||
return new AppliedGetSourcesRequest(getResponseFromLink(sourcesResponse, "_embedded.files.find{it.name=='" + fileName + "'}._links.self.href"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppliedGetDiffRequest extends AppliedGetRequest<AppliedGetDiffRequest> {
|
|
||||||
|
|
||||||
AppliedGetDiffRequest(Response response) {
|
|
||||||
super(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GivenUrl {
|
|
||||||
|
|
||||||
GivenWithUrlAndAuth usernameAndPassword(String username, String password) {
|
|
||||||
setUsername(username);
|
|
||||||
setPassword(password);
|
|
||||||
return new GivenWithUrlAndAuth();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppliedGetModificationsRequest extends AppliedGetRequest<AppliedGetModificationsRequest> {
|
|
||||||
public AppliedGetModificationsRequest(Response response) { super(response); }
|
|
||||||
ModificationsResponse usingModificationsResponse() {
|
|
||||||
return new ModificationsResponse(super.response);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class ModificationsResponse {
|
|
||||||
private Response resource;
|
|
||||||
|
|
||||||
public ModificationsResponse(Response resource) {
|
|
||||||
this.resource = resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModificationsResponse assertRevision(Consumer<String> assertRevision) {
|
|
||||||
String revision = resource.then().extract().path("revision");
|
|
||||||
assertRevision.accept(revision);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModificationsResponse assertAdded(Consumer<List<String>> assertAdded) {
|
|
||||||
List<String > added = resource.then().extract().path("added");
|
|
||||||
assertAdded.accept(added);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModificationsResponse assertRemoved(Consumer<List<String>> assertRemoved) {
|
|
||||||
List<String > removed = resource.then().extract().path("removed");
|
|
||||||
assertRemoved.accept(removed);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModificationsResponse assertModified(Consumer<List<String>> assertModified) {
|
|
||||||
List<String > modified = resource.then().extract().path("modified");
|
|
||||||
assertModified.accept(modified);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
113
scm-it/src/test/java/sonia/scm/it/UserITCase.java
Normal file
113
scm-it/src/test/java/sonia/scm/it/UserITCase.java
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package sonia.scm.it;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.it.utils.ScmRequests;
|
||||||
|
import sonia.scm.it.utils.TestData;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class UserITCase {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init(){
|
||||||
|
TestData.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void adminShouldChangeOwnPassword() {
|
||||||
|
String newUser = "user";
|
||||||
|
String password = "pass";
|
||||||
|
TestData.createUser(newUser, password, true, "xml");
|
||||||
|
String newPassword = "new_password";
|
||||||
|
// admin change the own password
|
||||||
|
ScmRequests.start()
|
||||||
|
.given()
|
||||||
|
.url(TestData.getUserUrl(newUser))
|
||||||
|
.usernameAndPassword(newUser, password)
|
||||||
|
.getUserResource()
|
||||||
|
.assertStatusCode(200)
|
||||||
|
.usingUserResponse()
|
||||||
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
||||||
|
.assertPassword(Assert::assertNull)
|
||||||
|
.requestChangePassword(newPassword) // the oldPassword is not needed in the user resource
|
||||||
|
.assertStatusCode(204);
|
||||||
|
// assert password is changed -> login with the new Password
|
||||||
|
ScmRequests.start()
|
||||||
|
.given()
|
||||||
|
.url(TestData.getUserUrl(newUser))
|
||||||
|
.usernameAndPassword(newUser, newPassword)
|
||||||
|
.getUserResource()
|
||||||
|
.assertStatusCode(200)
|
||||||
|
.usingUserResponse()
|
||||||
|
.assertAdmin(isAdmin -> assertThat(isAdmin).isEqualTo(Boolean.TRUE))
|
||||||
|
.assertPassword(Assert::assertNull);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void adminShouldChangePasswordOfOtherUser() {
|
||||||
|
String newUser = "user";
|
||||||
|
String password = "pass";
|
||||||
|
TestData.createUser(newUser, password, true, "xml");
|
||||||
|
String newPassword = "new_password";
|
||||||
|
// admin change the password of the user
|
||||||
|
ScmRequests.start()
|
||||||
|
.given()
|
||||||
|
.url(TestData.getUserUrl(newUser))// the admin get the user object
|
||||||
|
.usernameAndPassword(TestData.USER_SCM_ADMIN, TestData.USER_SCM_ADMIN)
|
||||||
|
.getUserResource()
|
||||||
|
.assertStatusCode(200)
|
||||||
|
.usingUserResponse()
|
||||||
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE)) // the user anonymous is not an admin
|
||||||
|
.assertPassword(Assert::assertNull)
|
||||||
|
.requestChangePassword(newPassword) // the oldPassword is not needed in the user resource
|
||||||
|
.assertStatusCode(204);
|
||||||
|
// assert password is changed
|
||||||
|
ScmRequests.start()
|
||||||
|
.given()
|
||||||
|
.url(TestData.getUserUrl(newUser))
|
||||||
|
.usernameAndPassword(newUser, newPassword)
|
||||||
|
.getUserResource()
|
||||||
|
.assertStatusCode(200);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldHidePasswordLinkIfUserTypeIsNotXML() {
|
||||||
|
String newUser = "user";
|
||||||
|
String password = "pass";
|
||||||
|
String type = "not XML Type";
|
||||||
|
TestData.createUser(newUser, password, true, type);
|
||||||
|
ScmRequests.start()
|
||||||
|
.given()
|
||||||
|
.url(TestData.getMeUrl())
|
||||||
|
.usernameAndPassword(newUser, password)
|
||||||
|
.getUserResource()
|
||||||
|
.assertStatusCode(200)
|
||||||
|
.usingUserResponse()
|
||||||
|
.assertAdmin(aBoolean -> assertThat(aBoolean).isEqualTo(Boolean.TRUE))
|
||||||
|
.assertPassword(Assert::assertNull)
|
||||||
|
.assertType(s -> assertThat(s).isEqualTo(type))
|
||||||
|
.assertPasswordLinkDoesNotExists();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGet403IfUserIsNotAdmin() {
|
||||||
|
String newUser = "user";
|
||||||
|
String password = "pass";
|
||||||
|
String type = "xml";
|
||||||
|
TestData.createUser(newUser, password, false, type);
|
||||||
|
ScmRequests.start()
|
||||||
|
.given()
|
||||||
|
.url(TestData.getMeUrl())
|
||||||
|
.usernameAndPassword(newUser, password)
|
||||||
|
.getUserResource()
|
||||||
|
.assertStatusCode(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package sonia.scm.it.utils;
|
||||||
|
|
||||||
|
import javax.json.JsonArrayBuilder;
|
||||||
|
import javax.json.JsonObject;
|
||||||
|
import javax.json.JsonObjectBuilder;
|
||||||
|
import javax.json.JsonValue;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
public class NullAwareJsonObjectBuilder implements JsonObjectBuilder {
|
||||||
|
public static JsonObjectBuilder wrap(JsonObjectBuilder builder) {
|
||||||
|
if (builder == null) {
|
||||||
|
throw new IllegalArgumentException("Can't wrap nothing.");
|
||||||
|
}
|
||||||
|
return new NullAwareJsonObjectBuilder(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final JsonObjectBuilder builder;
|
||||||
|
|
||||||
|
private NullAwareJsonObjectBuilder(JsonObjectBuilder builder) {
|
||||||
|
this.builder = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObjectBuilder add(String name, JsonValue value) {
|
||||||
|
return builder.add(name, (value == null) ? JsonValue.NULL : value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObjectBuilder add(String name, String value) {
|
||||||
|
if (value != null){
|
||||||
|
return builder.add(name, value );
|
||||||
|
}else{
|
||||||
|
return builder.addNull(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObjectBuilder add(String name, BigInteger value) {
|
||||||
|
if (value != null){
|
||||||
|
return builder.add(name, value );
|
||||||
|
}else{
|
||||||
|
return builder.addNull(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObjectBuilder add(String name, BigDecimal value) {
|
||||||
|
if (value != null){
|
||||||
|
return builder.add(name, value );
|
||||||
|
}else{
|
||||||
|
return builder.addNull(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObjectBuilder add(String s, int i) {
|
||||||
|
return builder.add(s, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObjectBuilder add(String s, long l) {
|
||||||
|
return builder.add(s, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObjectBuilder add(String s, double v) {
|
||||||
|
return builder.add(s, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObjectBuilder add(String s, boolean b) {
|
||||||
|
return builder.add(s, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObjectBuilder addNull(String s) {
|
||||||
|
return builder.addNull(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObjectBuilder add(String s, JsonObjectBuilder jsonObjectBuilder) {
|
||||||
|
return builder.add(s, jsonObjectBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObjectBuilder add(String s, JsonArrayBuilder jsonArrayBuilder) {
|
||||||
|
return builder.add(s, jsonArrayBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObject build() {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package sonia.scm.it;
|
package sonia.scm.it.utils;
|
||||||
|
|
||||||
import org.hamcrest.BaseMatcher;
|
import org.hamcrest.BaseMatcher;
|
||||||
import org.hamcrest.Description;
|
import org.hamcrest.Description;
|
||||||
@@ -6,7 +6,7 @@ import org.hamcrest.Matcher;
|
|||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
class RegExMatcher extends BaseMatcher<String> {
|
public class RegExMatcher extends BaseMatcher<String> {
|
||||||
public static Matcher<String> matchesPattern(String pattern) {
|
public static Matcher<String> matchesPattern(String pattern) {
|
||||||
return new RegExMatcher(pattern);
|
return new RegExMatcher(pattern);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package sonia.scm.it;
|
package sonia.scm.it.utils;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
@@ -24,11 +24,11 @@ public class RepositoryUtil {
|
|||||||
|
|
||||||
private static final RepositoryClientFactory REPOSITORY_CLIENT_FACTORY = new RepositoryClientFactory();
|
private static final RepositoryClientFactory REPOSITORY_CLIENT_FACTORY = new RepositoryClientFactory();
|
||||||
|
|
||||||
static RepositoryClient createRepositoryClient(String repositoryType, File folder) throws IOException {
|
public static RepositoryClient createRepositoryClient(String repositoryType, File folder) throws IOException {
|
||||||
return createRepositoryClient(repositoryType, folder, "scmadmin", "scmadmin");
|
return createRepositoryClient(repositoryType, folder, "scmadmin", "scmadmin");
|
||||||
}
|
}
|
||||||
|
|
||||||
static RepositoryClient createRepositoryClient(String repositoryType, File folder, String username, String password) throws IOException {
|
public static RepositoryClient createRepositoryClient(String repositoryType, File folder, String username, String password) throws IOException {
|
||||||
String httpProtocolUrl = TestData.callRepository(username, password, repositoryType, HttpStatus.SC_OK)
|
String httpProtocolUrl = TestData.callRepository(username, password, repositoryType, HttpStatus.SC_OK)
|
||||||
.extract()
|
.extract()
|
||||||
.path("_links.protocol.find{it.name=='http'}.href");
|
.path("_links.protocol.find{it.name=='http'}.href");
|
||||||
@@ -36,14 +36,14 @@ public class RepositoryUtil {
|
|||||||
return REPOSITORY_CLIENT_FACTORY.create(repositoryType, httpProtocolUrl, username, password, folder);
|
return REPOSITORY_CLIENT_FACTORY.create(repositoryType, httpProtocolUrl, username, password, folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String addAndCommitRandomFile(RepositoryClient client, String username) throws IOException {
|
public static String addAndCommitRandomFile(RepositoryClient client, String username) throws IOException {
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
String name = "file-" + uuid + ".uuid";
|
String name = "file-" + uuid + ".uuid";
|
||||||
createAndCommitFile(client, username, name, uuid);
|
createAndCommitFile(client, username, name, uuid);
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Changeset createAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException {
|
public static Changeset createAndCommitFile(RepositoryClient repositoryClient, String username, String fileName, String content) throws IOException {
|
||||||
writeAndAddFile(repositoryClient, fileName, content);
|
writeAndAddFile(repositoryClient, fileName, content);
|
||||||
return commit(repositoryClient, username, "added " + fileName);
|
return commit(repositoryClient, username, "added " + fileName);
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ public class RepositoryUtil {
|
|||||||
* @return the changeset with all modifications
|
* @return the changeset with all modifications
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
static Changeset commitMultipleFileModifications(RepositoryClient repositoryClient, String username, Map<String, String> addedFiles, Map<String, String> modifiedFiles, List<String> removedFiles) throws IOException {
|
public static Changeset commitMultipleFileModifications(RepositoryClient repositoryClient, String username, Map<String, String> addedFiles, Map<String, String> modifiedFiles, List<String> removedFiles) throws IOException {
|
||||||
for (String fileName : addedFiles.keySet()) {
|
for (String fileName : addedFiles.keySet()) {
|
||||||
writeAndAddFile(repositoryClient, fileName, addedFiles.get(fileName));
|
writeAndAddFile(repositoryClient, fileName, addedFiles.get(fileName));
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ public class RepositoryUtil {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Changeset removeAndCommitFile(RepositoryClient repositoryClient, String username, String fileName) throws IOException {
|
public static Changeset removeAndCommitFile(RepositoryClient repositoryClient, String username, String fileName) throws IOException {
|
||||||
deleteFileAndApplyRemoveCommand(repositoryClient, fileName);
|
deleteFileAndApplyRemoveCommand(repositoryClient, fileName);
|
||||||
return commit(repositoryClient, username, "removed " + fileName);
|
return commit(repositoryClient, username, "removed " + fileName);
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ public class RepositoryUtil {
|
|||||||
return changeset;
|
return changeset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Tag addTag(RepositoryClient repositoryClient, String revision, String tagName) throws IOException {
|
public static Tag addTag(RepositoryClient repositoryClient, String revision, String tagName) throws IOException {
|
||||||
if (repositoryClient.isCommandSupported(ClientCommand.TAG)) {
|
if (repositoryClient.isCommandSupported(ClientCommand.TAG)) {
|
||||||
Tag tag = repositoryClient.getTagCommand().setRevision(revision).tag(tagName, TestData.USER_SCM_ADMIN);
|
Tag tag = repositoryClient.getTagCommand().setRevision(revision).tag(tagName, TestData.USER_SCM_ADMIN);
|
||||||
if (repositoryClient.isCommandSupported(ClientCommand.PUSH)) {
|
if (repositoryClient.isCommandSupported(ClientCommand.PUSH)) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package sonia.scm.it;
|
package sonia.scm.it.utils;
|
||||||
|
|
||||||
import io.restassured.RestAssured;
|
import io.restassured.RestAssured;
|
||||||
import io.restassured.specification.RequestSpecification;
|
import io.restassured.specification.RequestSpecification;
|
||||||
465
scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java
Normal file
465
scm-it/src/test/java/sonia/scm/it/utils/ScmRequests.java
Normal file
@@ -0,0 +1,465 @@
|
|||||||
|
package sonia.scm.it.utils;
|
||||||
|
|
||||||
|
import io.restassured.RestAssured;
|
||||||
|
import io.restassured.response.Response;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static sonia.scm.it.utils.TestData.createPasswordChangeJson;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulate rest requests of a repository in builder pattern
|
||||||
|
* <p>
|
||||||
|
* A Get Request can be applied with the methods request*()
|
||||||
|
* These methods return a AppliedGet*Request object
|
||||||
|
* This object can be used to apply general assertions over the rest Assured response
|
||||||
|
* In the AppliedGet*Request classes there is a using*Response() method
|
||||||
|
* that return the *Response class containing specific operations related to the specific response
|
||||||
|
* the *Response class contains also the request*() method to apply the next GET request from a link in the response.
|
||||||
|
*/
|
||||||
|
public class ScmRequests {
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public static ScmRequests start() {
|
||||||
|
return new ScmRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Given given() {
|
||||||
|
return new Given();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a GET Request to the extracted url from the given link
|
||||||
|
*
|
||||||
|
* @param linkPropertyName the property name of link
|
||||||
|
* @param response the response containing the link
|
||||||
|
* @return the response of the GET request using the given link
|
||||||
|
*/
|
||||||
|
private Response applyGETRequestFromLink(Response response, String linkPropertyName) {
|
||||||
|
return applyGETRequest(response
|
||||||
|
.then()
|
||||||
|
.extract()
|
||||||
|
.path(linkPropertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a GET Request to the given <code>url</code> and return the response.
|
||||||
|
*
|
||||||
|
* @param url the url of the GET request
|
||||||
|
* @return the response of the GET request using the given <code>url</code>
|
||||||
|
*/
|
||||||
|
private Response applyGETRequest(String url) {
|
||||||
|
return RestAssured.given()
|
||||||
|
.auth().preemptive().basic(username, password)
|
||||||
|
.when()
|
||||||
|
.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a PUT Request to the extracted url from the given link
|
||||||
|
*
|
||||||
|
* @param response the response containing the link
|
||||||
|
* @param linkPropertyName the property name of link
|
||||||
|
* @param body
|
||||||
|
* @return the response of the PUT request using the given link
|
||||||
|
*/
|
||||||
|
private Response applyPUTRequestFromLink(Response response, String linkPropertyName, String content, String body) {
|
||||||
|
return applyPUTRequest(response
|
||||||
|
.then()
|
||||||
|
.extract()
|
||||||
|
.path(linkPropertyName), content, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a PUT Request to the given <code>url</code> and return the response.
|
||||||
|
*
|
||||||
|
* @param url the url of the PUT request
|
||||||
|
* @param mediaType
|
||||||
|
* @param body
|
||||||
|
* @return the response of the PUT request using the given <code>url</code>
|
||||||
|
*/
|
||||||
|
private Response applyPUTRequest(String url, String mediaType, String body) {
|
||||||
|
return RestAssured.given()
|
||||||
|
.auth().preemptive().basic(username, password)
|
||||||
|
.when()
|
||||||
|
.contentType(mediaType)
|
||||||
|
.accept(mediaType)
|
||||||
|
.body(body)
|
||||||
|
.put(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Given {
|
||||||
|
|
||||||
|
public GivenUrl url(String url) {
|
||||||
|
setUrl(url);
|
||||||
|
return new GivenUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GivenUrl url(URI url) {
|
||||||
|
setUrl(url.toString());
|
||||||
|
return new GivenUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GivenWithUrlAndAuth {
|
||||||
|
public AppliedMeRequest getMeResource() {
|
||||||
|
return new AppliedMeRequest(applyGETRequest(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppliedUserRequest getUserResource() {
|
||||||
|
return new AppliedUserRequest(applyGETRequest(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppliedRepositoryRequest getRepositoryResource() {
|
||||||
|
return new AppliedRepositoryRequest(
|
||||||
|
applyGETRequest(url)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppliedRequest<SELF extends AppliedRequest> {
|
||||||
|
private Response response;
|
||||||
|
|
||||||
|
public AppliedRequest(Response response) {
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apply custom assertions to the actual response
|
||||||
|
*
|
||||||
|
* @param consumer consume the response in order to assert the content. the header, the payload etc..
|
||||||
|
* @return the self object
|
||||||
|
*/
|
||||||
|
public SELF assertResponse(Consumer<Response> consumer) {
|
||||||
|
consumer.accept(response);
|
||||||
|
return (SELF) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* special assertion of the status code
|
||||||
|
*
|
||||||
|
* @param expectedStatusCode the expected status code
|
||||||
|
* @return the self object
|
||||||
|
*/
|
||||||
|
public SELF assertStatusCode(int expectedStatusCode) {
|
||||||
|
this.response.then().assertThat().statusCode(expectedStatusCode);
|
||||||
|
return (SELF) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppliedRepositoryRequest extends AppliedRequest<AppliedRepositoryRequest> {
|
||||||
|
|
||||||
|
public AppliedRepositoryRequest(Response response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RepositoryResponse usingRepositoryResponse() {
|
||||||
|
return new RepositoryResponse(super.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RepositoryResponse {
|
||||||
|
|
||||||
|
private Response repositoryResponse;
|
||||||
|
|
||||||
|
public RepositoryResponse(Response repositoryResponse) {
|
||||||
|
this.repositoryResponse = repositoryResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppliedSourcesRequest requestSources() {
|
||||||
|
return new AppliedSourcesRequest(applyGETRequestFromLink(repositoryResponse, "_links.sources.href"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppliedChangesetsRequest requestChangesets() {
|
||||||
|
return new AppliedChangesetsRequest(applyGETRequestFromLink(repositoryResponse, "_links.changesets.href"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppliedChangesetsRequest extends AppliedRequest<AppliedChangesetsRequest> {
|
||||||
|
|
||||||
|
public AppliedChangesetsRequest(Response response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChangesetsResponse usingChangesetsResponse() {
|
||||||
|
return new ChangesetsResponse(super.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChangesetsResponse {
|
||||||
|
private Response changesetsResponse;
|
||||||
|
|
||||||
|
public ChangesetsResponse(Response changesetsResponse) {
|
||||||
|
this.changesetsResponse = changesetsResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChangesetsResponse assertChangesets(Consumer<List<Map>> changesetsConsumer) {
|
||||||
|
List<Map> changesets = changesetsResponse.then().extract().path("_embedded.changesets");
|
||||||
|
changesetsConsumer.accept(changesets);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppliedDiffRequest requestDiff(String revision) {
|
||||||
|
return new AppliedDiffRequest(applyGETRequestFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.diff.href"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppliedModificationsRequest requestModifications(String revision) {
|
||||||
|
return new AppliedModificationsRequest(applyGETRequestFromLink(changesetsResponse, "_embedded.changesets.find{it.id=='" + revision + "'}._links.modifications.href"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppliedSourcesRequest extends AppliedRequest<AppliedSourcesRequest> {
|
||||||
|
|
||||||
|
public AppliedSourcesRequest(Response sourcesResponse) {
|
||||||
|
super(sourcesResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourcesResponse usingSourcesResponse() {
|
||||||
|
return new SourcesResponse(super.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SourcesResponse {
|
||||||
|
|
||||||
|
private Response sourcesResponse;
|
||||||
|
|
||||||
|
public SourcesResponse(Response sourcesResponse) {
|
||||||
|
this.sourcesResponse = sourcesResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourcesResponse assertRevision(Consumer<String> assertRevision) {
|
||||||
|
String revision = sourcesResponse.then().extract().path("revision");
|
||||||
|
assertRevision.accept(revision);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourcesResponse assertFiles(Consumer<List> assertFiles) {
|
||||||
|
List files = sourcesResponse.then().extract().path("files");
|
||||||
|
assertFiles.accept(files);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppliedChangesetsRequest requestFileHistory(String fileName) {
|
||||||
|
return new AppliedChangesetsRequest(applyGETRequestFromLink(sourcesResponse, "_embedded.files.find{it.name=='" + fileName + "'}._links.history.href"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppliedSourcesRequest requestSelf(String fileName) {
|
||||||
|
return new AppliedSourcesRequest(applyGETRequestFromLink(sourcesResponse, "_embedded.files.find{it.name=='" + fileName + "'}._links.self.href"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppliedDiffRequest extends AppliedRequest<AppliedDiffRequest> {
|
||||||
|
|
||||||
|
public AppliedDiffRequest(Response response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GivenUrl {
|
||||||
|
|
||||||
|
public GivenWithUrlAndAuth usernameAndPassword(String username, String password) {
|
||||||
|
setUsername(username);
|
||||||
|
setPassword(password);
|
||||||
|
return new GivenWithUrlAndAuth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppliedModificationsRequest extends AppliedRequest<AppliedModificationsRequest> {
|
||||||
|
public AppliedModificationsRequest(Response response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModificationsResponse usingModificationsResponse() {
|
||||||
|
return new ModificationsResponse(super.response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModificationsResponse {
|
||||||
|
private Response resource;
|
||||||
|
|
||||||
|
public ModificationsResponse(Response resource) {
|
||||||
|
this.resource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModificationsResponse assertRevision(Consumer<String> assertRevision) {
|
||||||
|
String revision = resource.then().extract().path("revision");
|
||||||
|
assertRevision.accept(revision);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModificationsResponse assertAdded(Consumer<List<String>> assertAdded) {
|
||||||
|
List<String> added = resource.then().extract().path("added");
|
||||||
|
assertAdded.accept(added);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModificationsResponse assertRemoved(Consumer<List<String>> assertRemoved) {
|
||||||
|
List<String> removed = resource.then().extract().path("removed");
|
||||||
|
assertRemoved.accept(removed);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModificationsResponse assertModified(Consumer<List<String>> assertModified) {
|
||||||
|
List<String> modified = resource.then().extract().path("modified");
|
||||||
|
assertModified.accept(modified);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppliedMeRequest extends AppliedRequest<AppliedMeRequest> {
|
||||||
|
|
||||||
|
public AppliedMeRequest(Response response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MeResponse usingMeResponse() {
|
||||||
|
return new MeResponse(super.response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MeResponse extends UserResponse<MeResponse> {
|
||||||
|
|
||||||
|
|
||||||
|
public MeResponse(Response response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppliedChangePasswordRequest requestChangePassword(String oldPassword, String newPassword) {
|
||||||
|
return new AppliedChangePasswordRequest(applyPUTRequestFromLink(super.response, "_links.password.href", VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(oldPassword, newPassword)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserResponse<SELF extends UserResponse> extends ModelResponse<SELF> {
|
||||||
|
|
||||||
|
public static final String LINKS_PASSWORD_HREF = "_links.password.href";
|
||||||
|
|
||||||
|
public UserResponse(Response response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF assertPassword(Consumer<String> assertPassword) {
|
||||||
|
return super.assertSingleProperty(assertPassword, "password");
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF assertType(Consumer<String> assertType) {
|
||||||
|
return assertSingleProperty(assertType, "type");
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF assertAdmin(Consumer<Boolean> assertAdmin) {
|
||||||
|
return assertSingleProperty(assertAdmin, "admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF assertPasswordLinkDoesNotExists() {
|
||||||
|
return assertPropertyPathDoesNotExists(LINKS_PASSWORD_HREF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF assertPasswordLinkExists() {
|
||||||
|
return assertPropertyPathExists(LINKS_PASSWORD_HREF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppliedChangePasswordRequest requestChangePassword(String newPassword) {
|
||||||
|
return new AppliedChangePasswordRequest(applyPUTRequestFromLink(super.response, LINKS_PASSWORD_HREF, VndMediaType.PASSWORD_CHANGE, createPasswordChangeJson(null, newPassword)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* encapsulate standard assertions over model properties
|
||||||
|
*/
|
||||||
|
public class ModelResponse<SELF extends ModelResponse> {
|
||||||
|
|
||||||
|
protected Response response;
|
||||||
|
|
||||||
|
public ModelResponse(Response response) {
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> SELF assertSingleProperty(Consumer<T> assertSingleProperty, String propertyJsonPath) {
|
||||||
|
T propertyValue = response.then().extract().path(propertyJsonPath);
|
||||||
|
assertSingleProperty.accept(propertyValue);
|
||||||
|
return (SELF) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF assertPropertyPathExists(String propertyJsonPath) {
|
||||||
|
response.then().assertThat().body("any { it.containsKey('" + propertyJsonPath + "')}", is(true));
|
||||||
|
return (SELF) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF assertPropertyPathDoesNotExists(String propertyJsonPath) {
|
||||||
|
response.then().assertThat().body("this.any { it.containsKey('" + propertyJsonPath + "')}", is(false));
|
||||||
|
return (SELF) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SELF assertArrayProperty(Consumer<List> assertProperties, String propertyJsonPath) {
|
||||||
|
List properties = response.then().extract().path(propertyJsonPath);
|
||||||
|
assertProperties.accept(properties);
|
||||||
|
return (SELF) this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppliedChangePasswordRequest extends AppliedRequest<AppliedChangePasswordRequest> {
|
||||||
|
|
||||||
|
public AppliedChangePasswordRequest(Response response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppliedUserRequest extends AppliedRequest<AppliedUserRequest> {
|
||||||
|
|
||||||
|
public AppliedUserRequest(Response response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserResponse usingUserResponse() {
|
||||||
|
return new UserResponse(super.response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package sonia.scm.it;
|
package sonia.scm.it.utils;
|
||||||
|
|
||||||
import sonia.scm.util.IOUtil;
|
import sonia.scm.util.IOUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
class ScmTypes {
|
public class ScmTypes {
|
||||||
static Collection<String> availableScmTypes() {
|
public static Collection<String> availableScmTypes() {
|
||||||
Collection<String> params = new ArrayList<>();
|
Collection<String> params = new ArrayList<>();
|
||||||
|
|
||||||
params.add("git");
|
params.add("git");
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package sonia.scm.it;
|
package sonia.scm.it.utils;
|
||||||
|
|
||||||
import io.restassured.response.ValidatableResponse;
|
import io.restassured.response.ValidatableResponse;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
@@ -8,14 +8,16 @@ import sonia.scm.repository.PermissionType;
|
|||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.json.Json;
|
import javax.json.Json;
|
||||||
|
import javax.json.JsonObjectBuilder;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static sonia.scm.it.RestUtil.createResourceUrl;
|
import static sonia.scm.it.utils.RestUtil.createResourceUrl;
|
||||||
import static sonia.scm.it.RestUtil.given;
|
import static sonia.scm.it.utils.RestUtil.given;
|
||||||
import static sonia.scm.it.ScmTypes.availableScmTypes;
|
import static sonia.scm.it.utils.ScmTypes.availableScmTypes;
|
||||||
|
|
||||||
public class TestData {
|
public class TestData {
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ public class TestData {
|
|||||||
private static final List<String> PROTECTED_USERS = asList(USER_SCM_ADMIN, USER_ANONYMOUS);
|
private static final List<String> PROTECTED_USERS = asList(USER_SCM_ADMIN, USER_ANONYMOUS);
|
||||||
|
|
||||||
private static Map<String, String> DEFAULT_REPOSITORIES = new HashMap<>();
|
private static Map<String, String> DEFAULT_REPOSITORIES = new HashMap<>();
|
||||||
|
public static final JsonObjectBuilder JSON_BUILDER = NullAwareJsonObjectBuilder.wrap(Json.createObjectBuilder());
|
||||||
|
|
||||||
public static void createDefault() {
|
public static void createDefault() {
|
||||||
cleanup();
|
cleanup();
|
||||||
@@ -44,27 +47,31 @@ public class TestData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void createUser(String username, String password) {
|
public static void createUser(String username, String password) {
|
||||||
|
createUser(username, password, false, "xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void createUser(String username, String password, boolean isAdmin, String type) {
|
||||||
LOG.info("create user with username: {}", username);
|
LOG.info("create user with username: {}", username);
|
||||||
|
String admin = isAdmin ? "true" : "false";
|
||||||
given(VndMediaType.USER)
|
given(VndMediaType.USER)
|
||||||
.when()
|
.when()
|
||||||
.content(" {\n" +
|
.content(new StringBuilder()
|
||||||
" \"active\": true,\n" +
|
.append(" {\n")
|
||||||
" \"admin\": false,\n" +
|
.append(" \"active\": true,\n")
|
||||||
" \"creationDate\": \"2018-08-21T12:26:46.084Z\",\n" +
|
.append(" \"admin\": ").append(admin).append(",\n")
|
||||||
" \"displayName\": \"" + username + "\",\n" +
|
.append(" \"creationDate\": \"2018-08-21T12:26:46.084Z\",\n")
|
||||||
" \"mail\": \"user1@scm-manager.org\",\n" +
|
.append(" \"displayName\": \"").append(username).append("\",\n")
|
||||||
" \"name\": \"" + username + "\",\n" +
|
.append(" \"mail\": \"user1@scm-manager.org\",\n")
|
||||||
" \"password\": \"" + password + "\",\n" +
|
.append(" \"name\": \"").append(username).append("\",\n")
|
||||||
" \"type\": \"xml\"\n" +
|
.append(" \"password\": \"").append(password).append("\",\n")
|
||||||
" \n" +
|
.append(" \"type\": \"").append(type).append("\"\n")
|
||||||
" }")
|
.append(" }").toString())
|
||||||
.post(createResourceUrl("users"))
|
.post(getUsersUrl())
|
||||||
.then()
|
.then()
|
||||||
.statusCode(HttpStatus.SC_CREATED)
|
.statusCode(HttpStatus.SC_CREATED)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void createUserPermission(String name, PermissionType permissionType, String repositoryType) {
|
public static void createUserPermission(String name, PermissionType permissionType, String repositoryType) {
|
||||||
String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType);
|
String defaultPermissionUrl = TestData.getDefaultPermissionUrl(USER_SCM_ADMIN, USER_SCM_ADMIN, repositoryType);
|
||||||
LOG.info("create permission with name {} and type: {} using the endpoint: {}", name, permissionType, defaultPermissionUrl);
|
LOG.info("create permission with name {} and type: {} using the endpoint: {}", name, permissionType, defaultPermissionUrl);
|
||||||
@@ -183,7 +190,7 @@ public class TestData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String repositoryJson(String repositoryType) {
|
public static String repositoryJson(String repositoryType) {
|
||||||
return Json.createObjectBuilder()
|
return JSON_BUILDER
|
||||||
.add("contact", "zaphod.beeblebrox@hitchhiker.com")
|
.add("contact", "zaphod.beeblebrox@hitchhiker.com")
|
||||||
.add("description", "Heart of Gold")
|
.add("description", "Heart of Gold")
|
||||||
.add("name", "HeartOfGold-" + repositoryType)
|
.add("name", "HeartOfGold-" + repositoryType)
|
||||||
@@ -192,6 +199,29 @@ public class TestData {
|
|||||||
.build().toString();
|
.build().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static URI getMeUrl() {
|
||||||
|
return RestUtil.createResourceUrl("me/");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static URI getUsersUrl() {
|
||||||
|
return RestUtil.createResourceUrl("users/");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static URI getUserUrl(String username) {
|
||||||
|
return getUsersUrl().resolve(username);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String createPasswordChangeJson(String oldPassword, String newPassword) {
|
||||||
|
return JSON_BUILDER
|
||||||
|
.add("oldPassword", oldPassword)
|
||||||
|
.add("newPassword", newPassword)
|
||||||
|
.build().toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { repositories } from "@scm-manager/ui-components";
|
||||||
import type { Repository } from "@scm-manager/ui-types";
|
import type { Repository } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -10,14 +11,16 @@ class ProtocolInformation extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { repository } = this.props;
|
const { repository } = this.props;
|
||||||
if (!repository._links.httpProtocol) {
|
const href = repositories.getProtocolLinkByType(repository, "http");
|
||||||
|
if (!href) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4>Clone the repository</h4>
|
<h4>Clone the repository</h4>
|
||||||
<pre>
|
<pre>
|
||||||
<code>git clone {repository._links.httpProtocol.href}</code>
|
<code>git clone {href}</code>
|
||||||
</pre>
|
</pre>
|
||||||
<h4>Create a new repository</h4>
|
<h4>Create a new repository</h4>
|
||||||
<pre>
|
<pre>
|
||||||
@@ -30,7 +33,7 @@ class ProtocolInformation extends React.Component<Props> {
|
|||||||
<br />
|
<br />
|
||||||
git commit -m "added readme"
|
git commit -m "added readme"
|
||||||
<br />
|
<br />
|
||||||
git remote add origin {repository._links.httpProtocol.href}
|
git remote add origin {href}
|
||||||
<br />
|
<br />
|
||||||
git push -u origin master
|
git push -u origin master
|
||||||
<br />
|
<br />
|
||||||
@@ -39,7 +42,7 @@ class ProtocolInformation extends React.Component<Props> {
|
|||||||
<h4>Push an existing repository</h4>
|
<h4>Push an existing repository</h4>
|
||||||
<pre>
|
<pre>
|
||||||
<code>
|
<code>
|
||||||
git remote add origin {repository._links.httpProtocol.href}
|
git remote add origin {href}
|
||||||
<br />
|
<br />
|
||||||
git push -u origin master
|
git push -u origin master
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { repositories } from "@scm-manager/ui-components";
|
||||||
import type { Repository } from "@scm-manager/ui-types";
|
import type { Repository } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -10,14 +11,15 @@ class ProtocolInformation extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { repository } = this.props;
|
const { repository } = this.props;
|
||||||
if (!repository._links.httpProtocol) {
|
const href = repositories.getProtocolLinkByType(repository, "http");
|
||||||
|
if (!href) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4>Clone the repository</h4>
|
<h4>Clone the repository</h4>
|
||||||
<pre>
|
<pre>
|
||||||
<code>hg clone {repository._links.httpProtocol.href}</code>
|
<code>hg clone {href}</code>
|
||||||
</pre>
|
</pre>
|
||||||
<h4>Create a new repository</h4>
|
<h4>Create a new repository</h4>
|
||||||
<pre>
|
<pre>
|
||||||
@@ -26,7 +28,7 @@ class ProtocolInformation extends React.Component<Props> {
|
|||||||
<br />
|
<br />
|
||||||
echo "[paths]" > .hg/hgrc
|
echo "[paths]" > .hg/hgrc
|
||||||
<br />
|
<br />
|
||||||
echo "default = {repository._links.httpProtocol.href}" > .hg/hgrc
|
echo "default = {href}" > .hg/hgrc
|
||||||
<br />
|
<br />
|
||||||
echo "# {repository.name}" > README.md
|
echo "# {repository.name}" > README.md
|
||||||
<br />
|
<br />
|
||||||
@@ -44,7 +46,7 @@ class ProtocolInformation extends React.Component<Props> {
|
|||||||
<code>
|
<code>
|
||||||
# add the repository url as default to your .hg/hgrc e.g:
|
# add the repository url as default to your .hg/hgrc e.g:
|
||||||
<br />
|
<br />
|
||||||
default = {repository._links.httpProtocol.href}
|
default = {href}
|
||||||
<br />
|
<br />
|
||||||
# push to remote repository
|
# push to remote repository
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { repositories } from "@scm-manager/ui-components";
|
||||||
import type { Repository } from "@scm-manager/ui-types";
|
import type { Repository } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -10,14 +11,15 @@ class ProtocolInformation extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { repository } = this.props;
|
const { repository } = this.props;
|
||||||
if (!repository._links.httpProtocol) {
|
const href = repositories.getProtocolLinkByType(repository, "http");
|
||||||
|
if (!href) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4>Checkout the repository</h4>
|
<h4>Checkout the repository</h4>
|
||||||
<pre>
|
<pre>
|
||||||
<code>svn checkout {repository._links.httpProtocol.href}</code>
|
<code>svn checkout {href}</code>
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
import * as validation from "./validation.js";
|
import * as validation from "./validation.js";
|
||||||
import * as urls from "./urls";
|
import * as urls from "./urls";
|
||||||
|
import * as repositories from "./repositories.js";
|
||||||
|
|
||||||
export { validation, urls };
|
export { validation, urls, repositories };
|
||||||
|
|
||||||
export { default as DateFromNow } from "./DateFromNow.js";
|
export { default as DateFromNow } from "./DateFromNow.js";
|
||||||
export { default as ErrorNotification } from "./ErrorNotification.js";
|
export { default as ErrorNotification } from "./ErrorNotification.js";
|
||||||
@@ -18,6 +19,8 @@ export { default as ProtectedRoute } from "./ProtectedRoute.js";
|
|||||||
|
|
||||||
export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR } from "./apiclient.js";
|
export { apiClient, NOT_FOUND_ERROR, UNAUTHORIZED_ERROR } from "./apiclient.js";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export * from "./buttons";
|
export * from "./buttons";
|
||||||
export * from "./forms";
|
export * from "./forms";
|
||||||
export * from "./layout";
|
export * from "./layout";
|
||||||
|
|||||||
19
scm-ui-components/packages/ui-components/src/repositories.js
Normal file
19
scm-ui-components/packages/ui-components/src/repositories.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// @flow
|
||||||
|
import type { Repository } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
|
// util methods for repositories
|
||||||
|
|
||||||
|
export function getProtocolLinkByType(repository: Repository, type: string) {
|
||||||
|
let protocols = repository._links.protocol;
|
||||||
|
if (protocols) {
|
||||||
|
if (!Array.isArray(protocols)) {
|
||||||
|
protocols = [protocols];
|
||||||
|
}
|
||||||
|
for (let proto of protocols) {
|
||||||
|
if (proto.name === type) {
|
||||||
|
return proto.href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import type { Repository } from "@scm-manager/ui-types";
|
||||||
|
import { getProtocolLinkByType, getTypePredicate } from "./repositories";
|
||||||
|
|
||||||
|
describe("getProtocolLinkByType tests", () => {
|
||||||
|
|
||||||
|
it("should return the http protocol link", () => {
|
||||||
|
|
||||||
|
const repository: Repository = {
|
||||||
|
namespace: "scm",
|
||||||
|
name: "core",
|
||||||
|
type: "git",
|
||||||
|
_links: {
|
||||||
|
protocol: [{
|
||||||
|
name: "http",
|
||||||
|
href: "http://scm.scm-manager.org/repo/scm/core"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const link = getProtocolLinkByType(repository, "http");
|
||||||
|
expect(link).toBe("http://scm.scm-manager.org/repo/scm/core");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the http protocol link from multiple protocols", () => {
|
||||||
|
|
||||||
|
const repository: Repository = {
|
||||||
|
namespace: "scm",
|
||||||
|
name: "core",
|
||||||
|
type: "git",
|
||||||
|
_links: {
|
||||||
|
protocol: [{
|
||||||
|
name: "http",
|
||||||
|
href: "http://scm.scm-manager.org/repo/scm/core"
|
||||||
|
},{
|
||||||
|
name: "ssh",
|
||||||
|
href: "git@scm.scm-manager.org:scm/core"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const link = getProtocolLinkByType(repository, "http");
|
||||||
|
expect(link).toBe("http://scm.scm-manager.org/repo/scm/core");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the http protocol, even if the protocol is a single link", () => {
|
||||||
|
|
||||||
|
const repository: Repository = {
|
||||||
|
namespace: "scm",
|
||||||
|
name: "core",
|
||||||
|
type: "git",
|
||||||
|
_links: {
|
||||||
|
protocol: {
|
||||||
|
name: "http",
|
||||||
|
href: "http://scm.scm-manager.org/repo/scm/core"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const link = getProtocolLinkByType(repository, "http");
|
||||||
|
expect(link).toBe("http://scm.scm-manager.org/repo/scm/core");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null, if such a protocol does not exists", () => {
|
||||||
|
|
||||||
|
const repository: Repository = {
|
||||||
|
namespace: "scm",
|
||||||
|
name: "core",
|
||||||
|
type: "git",
|
||||||
|
_links: {
|
||||||
|
protocol: [{
|
||||||
|
name: "http",
|
||||||
|
href: "http://scm.scm-manager.org/repo/scm/core"
|
||||||
|
},{
|
||||||
|
name: "ssh",
|
||||||
|
href: "git@scm.scm-manager.org:scm/core"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const link = getProtocolLinkByType(repository, "awesome");
|
||||||
|
expect(link).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null, if no protocols are available", () => {
|
||||||
|
|
||||||
|
const repository: Repository = {
|
||||||
|
namespace: "scm",
|
||||||
|
name: "core",
|
||||||
|
type: "git",
|
||||||
|
_links: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const link = getProtocolLinkByType(repository, "http");
|
||||||
|
expect(link).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
// @flow
|
// @flow
|
||||||
export type Link = {
|
export type Link = {
|
||||||
href: string
|
href: string,
|
||||||
|
name?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Links = { [string]: Link };
|
export type Links = { [string]: Link | Link[] };
|
||||||
|
|
||||||
export type Collection = {
|
export type Collection = {
|
||||||
_embedded: Object,
|
_embedded: Object,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const userZaphod = {
|
|||||||
displayName: "Z. Beeblebrox",
|
displayName: "Z. Beeblebrox",
|
||||||
mail: "president@heartofgold.universe",
|
mail: "president@heartofgold.universe",
|
||||||
name: "zaphod",
|
name: "zaphod",
|
||||||
password: "__dummypassword__",
|
password: "",
|
||||||
type: "xml",
|
type: "xml",
|
||||||
properties: {},
|
properties: {},
|
||||||
_links: {
|
_links: {
|
||||||
@@ -79,7 +79,7 @@ const userFord = {
|
|||||||
displayName: "F. Prefect",
|
displayName: "F. Prefect",
|
||||||
mail: "ford@prefect.universe",
|
mail: "ford@prefect.universe",
|
||||||
name: "ford",
|
name: "ford",
|
||||||
password: "__dummypassword__",
|
password: "",
|
||||||
type: "xml",
|
type: "xml",
|
||||||
properties: {},
|
properties: {},
|
||||||
_links: {
|
_links: {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import sonia.scm.PageResult;
|
||||||
|
import sonia.scm.repository.Changeset;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
public class BranchChangesetCollectionToDtoMapper extends ChangesetCollectionToDtoMapperBase {
|
||||||
|
|
||||||
|
private final ResourceLinks resourceLinks;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public BranchChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
|
||||||
|
super(changesetToChangesetDtoMapper);
|
||||||
|
this.resourceLinks = resourceLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository, String branch) {
|
||||||
|
return this.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository, branch));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createSelfLink(Repository repository, String branch) {
|
||||||
|
return resourceLinks.branch().history(repository.getNamespaceAndName(), branch);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package sonia.scm.api.v2.resources;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
import sonia.scm.repository.Branches;
|
import sonia.scm.repository.Branches;
|
||||||
import sonia.scm.repository.Changeset;
|
import sonia.scm.repository.Changeset;
|
||||||
@@ -32,14 +33,14 @@ public class BranchRootResource {
|
|||||||
private final BranchToBranchDtoMapper branchToDtoMapper;
|
private final BranchToBranchDtoMapper branchToDtoMapper;
|
||||||
private final BranchCollectionToDtoMapper branchCollectionToDtoMapper;
|
private final BranchCollectionToDtoMapper branchCollectionToDtoMapper;
|
||||||
|
|
||||||
private final ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper;
|
private final BranchChangesetCollectionToDtoMapper branchChangesetCollectionToDtoMapper;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public BranchRootResource(RepositoryServiceFactory serviceFactory, BranchToBranchDtoMapper branchToDtoMapper, BranchCollectionToDtoMapper branchCollectionToDtoMapper, ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper) {
|
public BranchRootResource(RepositoryServiceFactory serviceFactory, BranchToBranchDtoMapper branchToDtoMapper, BranchCollectionToDtoMapper branchCollectionToDtoMapper, BranchChangesetCollectionToDtoMapper changesetCollectionToDtoMapper) {
|
||||||
this.serviceFactory = serviceFactory;
|
this.serviceFactory = serviceFactory;
|
||||||
this.branchToDtoMapper = branchToDtoMapper;
|
this.branchToDtoMapper = branchToDtoMapper;
|
||||||
this.branchCollectionToDtoMapper = branchCollectionToDtoMapper;
|
this.branchCollectionToDtoMapper = branchCollectionToDtoMapper;
|
||||||
this.changesetCollectionToDtoMapper = changesetCollectionToDtoMapper;
|
this.branchChangesetCollectionToDtoMapper = changesetCollectionToDtoMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,6 +99,14 @@ public class BranchRootResource {
|
|||||||
@DefaultValue("0") @QueryParam("page") int page,
|
@DefaultValue("0") @QueryParam("page") int page,
|
||||||
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws Exception {
|
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws Exception {
|
||||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||||
|
boolean branchExists = repositoryService.getBranchesCommand()
|
||||||
|
.getBranches()
|
||||||
|
.getBranches()
|
||||||
|
.stream()
|
||||||
|
.anyMatch(branch -> branchName.equals(branch.getName()));
|
||||||
|
if (!branchExists){
|
||||||
|
throw new NotFoundException("branch", branchName);
|
||||||
|
}
|
||||||
Repository repository = repositoryService.getRepository();
|
Repository repository = repositoryService.getRepository();
|
||||||
RepositoryPermissions.read(repository).check();
|
RepositoryPermissions.read(repository).check();
|
||||||
ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService)
|
ChangesetPagingResult changesets = new PagedLogCommandBuilder(repositoryService)
|
||||||
@@ -108,7 +117,7 @@ public class BranchRootResource {
|
|||||||
.getChangesets();
|
.getChangesets();
|
||||||
if (changesets != null && changesets.getChangesets() != null) {
|
if (changesets != null && changesets.getChangesets() != null) {
|
||||||
PageResult<Changeset> pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal());
|
PageResult<Changeset> pageResult = new PageResult<>(changesets.getChangesets(), changesets.getTotal());
|
||||||
return Response.ok(changesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository)).build();
|
return Response.ok(branchChangesetCollectionToDtoMapper.map(page, pageSize, pageResult, repository, branchName)).build();
|
||||||
} else {
|
} else {
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import sonia.scm.user.ChangePasswordNotAllowedException;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.ext.ExceptionMapper;
|
||||||
|
import javax.ws.rs.ext.Provider;
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
public class ChangePasswordNotAllowedExceptionMapper implements ExceptionMapper<ChangePasswordNotAllowedException> {
|
||||||
|
@Override
|
||||||
|
public Response toResponse(ChangePasswordNotAllowedException exception) {
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(exception.getMessage())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,31 +5,22 @@ import sonia.scm.repository.Changeset;
|
|||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class ChangesetCollectionToDtoMapper extends PagedCollectionToDtoMapper<Changeset, ChangesetDto> {
|
public class ChangesetCollectionToDtoMapper extends ChangesetCollectionToDtoMapperBase {
|
||||||
|
|
||||||
private final ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper;
|
private final ResourceLinks resourceLinks;
|
||||||
protected final ResourceLinks resourceLinks;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
|
public ChangesetCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
|
||||||
super("changesets");
|
super(changesetToChangesetDtoMapper);
|
||||||
this.changesetToChangesetDtoMapper = changesetToChangesetDtoMapper;
|
|
||||||
this.resourceLinks = resourceLinks;
|
this.resourceLinks = resourceLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository) {
|
public CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository) {
|
||||||
return this.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository));
|
return super.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository, Supplier<String> selfLinkSupplier) {
|
private String createSelfLink(Repository repository) {
|
||||||
return super.map(pageNumber, pageSize, pageResult, selfLinkSupplier.get(), Optional.empty(), changeset -> changesetToChangesetDtoMapper.map(changeset, repository));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String createSelfLink(Repository repository) {
|
|
||||||
return resourceLinks.changeset().all(repository.getNamespace(), repository.getName());
|
return resourceLinks.changeset().all(repository.getNamespace(), repository.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import sonia.scm.PageResult;
|
||||||
|
import sonia.scm.repository.Changeset;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
class ChangesetCollectionToDtoMapperBase extends PagedCollectionToDtoMapper<Changeset, ChangesetDto> {
|
||||||
|
|
||||||
|
private final ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper;
|
||||||
|
|
||||||
|
ChangesetCollectionToDtoMapperBase(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper) {
|
||||||
|
super("changesets");
|
||||||
|
this.changesetToChangesetDtoMapper = changesetToChangesetDtoMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository, Supplier<String> selfLinkSupplier) {
|
||||||
|
return super.map(pageNumber, pageSize, pageResult, selfLinkSupplier.get(), Optional.empty(), changeset -> changesetToChangesetDtoMapper.map(changeset, repository));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -6,19 +6,22 @@ import sonia.scm.repository.Repository;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
public class FileHistoryCollectionToDtoMapper extends ChangesetCollectionToDtoMapper {
|
public class FileHistoryCollectionToDtoMapper extends ChangesetCollectionToDtoMapperBase {
|
||||||
|
|
||||||
|
|
||||||
|
private final ResourceLinks resourceLinks;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FileHistoryCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
|
public FileHistoryCollectionToDtoMapper(ChangesetToChangesetDtoMapper changesetToChangesetDtoMapper, ResourceLinks resourceLinks) {
|
||||||
super(changesetToChangesetDtoMapper, resourceLinks);
|
super(changesetToChangesetDtoMapper);
|
||||||
|
this.resourceLinks = resourceLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository, String revision, String path) {
|
public CollectionDto map(int pageNumber, int pageSize, PageResult<Changeset> pageResult, Repository repository, String revision, String path) {
|
||||||
return super.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository, revision, path));
|
return super.map(pageNumber, pageSize, pageResult, repository, () -> createSelfLink(repository, revision, path));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String createSelfLink(Repository repository, String revision, String path) {
|
private String createSelfLink(Repository repository, String revision, String path) {
|
||||||
return super.resourceLinks.fileHistory().self(repository.getNamespace(), repository.getName(), revision, path);
|
return resourceLinks.fileHistory().self(repository.getNamespace(), repository.getName(), revision, path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import sonia.scm.PageResult;
|
|||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@@ -37,6 +38,15 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
|||||||
return singleAdapter.get(loadBy(id), mapToDto);
|
return singleAdapter.get(loadBy(id), mapToDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Consumer<MODEL_OBJECT> checker) throws NotFoundException, ConcurrentModificationException {
|
||||||
|
return singleAdapter.update(
|
||||||
|
loadBy(id),
|
||||||
|
applyChanges,
|
||||||
|
idStaysTheSame(id),
|
||||||
|
checker
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) throws NotFoundException, ConcurrentModificationException {
|
public Response update(String id, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges) throws NotFoundException, ConcurrentModificationException {
|
||||||
return singleAdapter.update(
|
return singleAdapter.update(
|
||||||
loadBy(id),
|
loadBy(id),
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import sonia.scm.user.InvalidPasswordException;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.ext.ExceptionMapper;
|
||||||
|
import javax.ws.rs.ext.Provider;
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
public class InvalidPasswordExceptionMapper implements ExceptionMapper<InvalidPasswordException> {
|
||||||
|
@Override
|
||||||
|
public Response toResponse(InvalidPasswordException exception) {
|
||||||
|
return Response.status(Response.Status.BAD_REQUEST)
|
||||||
|
.entity(exception.getMessage())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ public class MapperModule extends AbstractModule {
|
|||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
bind(UserDtoToUserMapper.class).to(Mappers.getMapper(UserDtoToUserMapper.class).getClass());
|
bind(UserDtoToUserMapper.class).to(Mappers.getMapper(UserDtoToUserMapper.class).getClass());
|
||||||
|
bind(MeToUserDtoMapper.class).to(Mappers.getMapper(MeToUserDtoMapper.class).getClass());
|
||||||
bind(UserToUserDtoMapper.class).to(Mappers.getMapper(UserToUserDtoMapper.class).getClass());
|
bind(UserToUserDtoMapper.class).to(Mappers.getMapper(UserToUserDtoMapper.class).getClass());
|
||||||
bind(UserCollectionToDtoMapper.class);
|
bind(UserCollectionToDtoMapper.class);
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,27 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.apache.shiro.authc.credential.PasswordService;
|
||||||
|
import sonia.scm.ConcurrentModificationException;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
|
import sonia.scm.user.InvalidPasswordException;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.PUT;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.Request;
|
import javax.ws.rs.core.Request;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static sonia.scm.user.InvalidPasswordException.INVALID_MATCHING;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,15 +32,20 @@ import javax.ws.rs.core.UriInfo;
|
|||||||
*/
|
*/
|
||||||
@Path(MeResource.ME_PATH_V2)
|
@Path(MeResource.ME_PATH_V2)
|
||||||
public class MeResource {
|
public class MeResource {
|
||||||
static final String ME_PATH_V2 = "v2/me/";
|
public static final String ME_PATH_V2 = "v2/me/";
|
||||||
|
|
||||||
private final UserToUserDtoMapper userToDtoMapper;
|
private final MeToUserDtoMapper meToUserDtoMapper;
|
||||||
|
|
||||||
private final IdResourceManagerAdapter<User, UserDto> adapter;
|
private final IdResourceManagerAdapter<User, UserDto> adapter;
|
||||||
|
private final PasswordService passwordService;
|
||||||
|
private final UserManager userManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MeResource(UserToUserDtoMapper userToDtoMapper, UserManager manager) {
|
public MeResource(MeToUserDtoMapper meToUserDtoMapper, UserManager manager, PasswordService passwordService) {
|
||||||
this.userToDtoMapper = userToDtoMapper;
|
this.meToUserDtoMapper = meToUserDtoMapper;
|
||||||
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
|
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
|
||||||
|
this.passwordService = passwordService;
|
||||||
|
this.userManager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +63,34 @@ public class MeResource {
|
|||||||
public Response get(@Context Request request, @Context UriInfo uriInfo) throws NotFoundException {
|
public Response get(@Context Request request, @Context UriInfo uriInfo) throws NotFoundException {
|
||||||
|
|
||||||
String id = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
String id = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
||||||
return adapter.get(id, userToDtoMapper::map);
|
return adapter.get(id, meToUserDtoMapper::map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change password of the current user
|
||||||
|
*/
|
||||||
|
@PUT
|
||||||
|
@Path("password")
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 204, condition = "update success"),
|
||||||
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
|
@Consumes(VndMediaType.PASSWORD_CHANGE)
|
||||||
|
public Response changePassword(PasswordChangeDto passwordChangeDto) throws NotFoundException, ConcurrentModificationException {
|
||||||
|
String name = (String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal();
|
||||||
|
return adapter.update(name, user -> user.changePassword(passwordService.encryptPassword(passwordChangeDto.getNewPassword())), userManager.getUserTypeChecker().andThen(getOldOriginalPasswordChecker(passwordChangeDto.getOldPassword())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match given old password from the dto with the stored password before updating
|
||||||
|
*/
|
||||||
|
private Consumer<User> getOldOriginalPasswordChecker(String oldPassword) {
|
||||||
|
return user -> {
|
||||||
|
if (!user.getPassword().equals(passwordService.encryptPassword(oldPassword))) {
|
||||||
|
throw new InvalidPasswordException(INVALID_MATCHING);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.Links;
|
||||||
|
import org.mapstruct.AfterMapping;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.MappingTarget;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
import sonia.scm.user.UserManager;
|
||||||
|
import sonia.scm.user.UserPermissions;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static de.otto.edison.hal.Link.link;
|
||||||
|
import static de.otto.edison.hal.Links.linkingTo;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public abstract class MeToUserDtoMapper extends UserToUserDtoMapper{
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private UserManager userManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ResourceLinks resourceLinks;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@AfterMapping
|
||||||
|
protected void appendLinks(User user, @MappingTarget UserDto target) {
|
||||||
|
Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self());
|
||||||
|
if (UserPermissions.delete(user).isPermitted()) {
|
||||||
|
linksBuilder.single(link("delete", resourceLinks.me().delete(target.getName())));
|
||||||
|
}
|
||||||
|
if (UserPermissions.modify(user).isPermitted()) {
|
||||||
|
linksBuilder.single(link("update", resourceLinks.me().update(target.getName())));
|
||||||
|
}
|
||||||
|
if (userManager.isTypeDefault(user)) {
|
||||||
|
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
|
||||||
|
}
|
||||||
|
target.add(linksBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class PasswordChangeDto {
|
||||||
|
|
||||||
|
private String oldPassword;
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
private String newPassword;
|
||||||
|
}
|
||||||
@@ -162,7 +162,7 @@ public class PermissionRootResource {
|
|||||||
RepositoryPermissions.permissionWrite(repository).check();
|
RepositoryPermissions.permissionWrite(repository).check();
|
||||||
String extractedPermissionName = getPermissionName(permissionName);
|
String extractedPermissionName = getPermissionName(permissionName);
|
||||||
if (!isPermissionExist(new PermissionDto(extractedPermissionName, isGroupPermission(permissionName)), repository)) {
|
if (!isPermissionExist(new PermissionDto(extractedPermissionName, isGroupPermission(permissionName)), repository)) {
|
||||||
throw new NotFoundException("the permission " + extractedPermissionName + " does not exist");
|
throw new NotFoundException("permission", extractedPermissionName);
|
||||||
}
|
}
|
||||||
permission.setGroupPermission(isGroupPermission(permissionName));
|
permission.setGroupPermission(isGroupPermission(permissionName));
|
||||||
if (!extractedPermissionName.equals(permission.getName())) {
|
if (!extractedPermissionName.equals(permission.getName())) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package sonia.scm.api.v2.resources;
|
|||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
class ResourceLinks {
|
class ResourceLinks {
|
||||||
@@ -85,7 +86,42 @@ class ResourceLinks {
|
|||||||
String update(String name) {
|
String update(String name) {
|
||||||
return userLinkBuilder.method("getUserResource").parameters(name).method("update").parameters().href();
|
return userLinkBuilder.method("getUserResource").parameters(name).method("update").parameters().href();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String passwordChange(String name) {
|
||||||
|
return userLinkBuilder.method("getUserResource").parameters(name).method("changePassword").parameters().href();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MeLinks me() {
|
||||||
|
return new MeLinks(scmPathInfoStore.get(), this.user());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MeLinks {
|
||||||
|
private final LinkBuilder meLinkBuilder;
|
||||||
|
private UserLinks userLinks;
|
||||||
|
|
||||||
|
MeLinks(ScmPathInfo pathInfo, UserLinks user) {
|
||||||
|
meLinkBuilder = new LinkBuilder(pathInfo, MeResource.class);
|
||||||
|
userLinks = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
String self() {
|
||||||
|
return meLinkBuilder.method("get").parameters().href();
|
||||||
|
}
|
||||||
|
|
||||||
|
String delete(String name) {
|
||||||
|
return userLinks.delete(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
String update(String name) {
|
||||||
|
return userLinks.update(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String passwordChange() {
|
||||||
|
return meLinkBuilder.method("changePassword").parameters().href();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
UserCollectionLinks userCollection() {
|
UserCollectionLinks userCollection() {
|
||||||
return new UserCollectionLinks(scmPathInfoStore.get());
|
return new UserCollectionLinks(scmPathInfoStore.get());
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import javax.ws.rs.core.GenericEntity;
|
|||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@@ -53,6 +54,11 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
|||||||
.map(Response.ResponseBuilder::build)
|
.map(Response.ResponseBuilder::build)
|
||||||
.orElseThrow(NotFoundException::new);
|
.orElseThrow(NotFoundException::new);
|
||||||
}
|
}
|
||||||
|
public Response update(Supplier<Optional<MODEL_OBJECT>> reader, Function<MODEL_OBJECT, MODEL_OBJECT> applyChanges, Predicate<MODEL_OBJECT> hasSameKey, Consumer<MODEL_OBJECT> checker) throws NotFoundException, ConcurrentModificationException {
|
||||||
|
MODEL_OBJECT existingModelObject = reader.get().orElseThrow(NotFoundException::new);
|
||||||
|
checker.accept(existingModelObject);
|
||||||
|
return update(reader,applyChanges,hasSameKey);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the model object for the given id according to the given function and returns a corresponding http response.
|
* Update the model object for the given id according to the given function and returns a corresponding http response.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public abstract class TagToTagDtoMapper {
|
|||||||
Links.Builder linksBuilder = linkingTo()
|
Links.Builder linksBuilder = linkingTo()
|
||||||
.self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName()))
|
.self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getName()))
|
||||||
.single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())))
|
.single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())))
|
||||||
.single(link("changesets", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())));
|
.single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), target.getRevision())));
|
||||||
target.add(linksBuilder.build());
|
target.add(linksBuilder.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
|
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
|
import org.apache.shiro.authc.credential.PasswordService;
|
||||||
import sonia.scm.AlreadyExistsException;
|
import sonia.scm.AlreadyExistsException;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
@@ -29,14 +30,16 @@ public class UserCollectionResource {
|
|||||||
private final ResourceLinks resourceLinks;
|
private final ResourceLinks resourceLinks;
|
||||||
|
|
||||||
private final IdResourceManagerAdapter<User, UserDto> adapter;
|
private final IdResourceManagerAdapter<User, UserDto> adapter;
|
||||||
|
private final PasswordService passwordService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UserCollectionResource(UserManager manager, UserDtoToUserMapper dtoToUserMapper,
|
public UserCollectionResource(UserManager manager, UserDtoToUserMapper dtoToUserMapper,
|
||||||
UserCollectionToDtoMapper userCollectionToDtoMapper, ResourceLinks resourceLinks) {
|
UserCollectionToDtoMapper userCollectionToDtoMapper, ResourceLinks resourceLinks, PasswordService passwordService) {
|
||||||
this.dtoToUserMapper = dtoToUserMapper;
|
this.dtoToUserMapper = dtoToUserMapper;
|
||||||
this.userCollectionToDtoMapper = userCollectionToDtoMapper;
|
this.userCollectionToDtoMapper = userCollectionToDtoMapper;
|
||||||
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
|
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
|
||||||
this.resourceLinks = resourceLinks;
|
this.resourceLinks = resourceLinks;
|
||||||
|
this.passwordService = passwordService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,8 +92,6 @@ public class UserCollectionResource {
|
|||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user"))
|
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created user"))
|
||||||
public Response create(@Valid UserDto userDto) throws AlreadyExistsException {
|
public Response create(@Valid UserDto userDto) throws AlreadyExistsException {
|
||||||
return adapter.create(userDto,
|
return adapter.create(userDto, () -> dtoToUserMapper.map(userDto, passwordService.encryptPassword(userDto.getPassword())), user -> resourceLinks.user().self(user.getName()));
|
||||||
() -> dtoToUserMapper.map(userDto, ""),
|
|
||||||
user -> resourceLinks.user().self(user.getName()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public class UserDto extends HalRepresentation {
|
|||||||
private String mail;
|
private String mail;
|
||||||
@Pattern(regexp = "^[A-z0-9\\.\\-_@]|[^ ]([A-z0-9\\.\\-_@ ]*[A-z0-9\\.\\-_@]|[^ ])?$")
|
@Pattern(regexp = "^[A-z0-9\\.\\-_@]|[^ ]([A-z0-9\\.\\-_@ ]*[A-z0-9\\.\\-_@]|[^ ])?$")
|
||||||
private String name;
|
private String name;
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
private String password;
|
private String password;
|
||||||
private String type;
|
private String type;
|
||||||
private Map<String, String> properties;
|
private Map<String, String> properties;
|
||||||
|
|||||||
@@ -1,37 +1,35 @@
|
|||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import org.apache.shiro.authc.credential.PasswordService;
|
import org.mapstruct.AfterMapping;
|
||||||
import org.mapstruct.Context;
|
import org.mapstruct.Context;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
import org.mapstruct.Mapping;
|
||||||
import org.mapstruct.Named;
|
import org.mapstruct.MappingTarget;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
import static sonia.scm.api.rest.resources.UserResource.DUMMY_PASSWORT;
|
|
||||||
|
|
||||||
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
|
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
|
||||||
@SuppressWarnings("squid:S3306")
|
@SuppressWarnings("squid:S3306")
|
||||||
@Mapper
|
@Mapper
|
||||||
public abstract class UserDtoToUserMapper extends BaseDtoMapper {
|
public abstract class UserDtoToUserMapper extends BaseDtoMapper {
|
||||||
|
|
||||||
@Inject
|
|
||||||
private PasswordService passwordService;
|
|
||||||
|
|
||||||
@Mapping(source = "password", target = "password", qualifiedByName = "encrypt")
|
|
||||||
@Mapping(target = "creationDate", ignore = true)
|
@Mapping(target = "creationDate", ignore = true)
|
||||||
public abstract User map(UserDto userDto, @Context String originalPassword);
|
public abstract User map(UserDto userDto, @Context String usedPassword);
|
||||||
|
|
||||||
@Named("encrypt")
|
|
||||||
String encrypt(String password, @Context String originalPassword) {
|
|
||||||
|
|
||||||
if (DUMMY_PASSWORT.equals(password)) {
|
/**
|
||||||
return originalPassword;
|
* depends on the use case the right password will be mapped.
|
||||||
} else {
|
* The given Password in the context parameter will be set.
|
||||||
return passwordService.encryptPassword(password);
|
* The mapper consumer have the control of what password should be set.
|
||||||
}
|
* </p>
|
||||||
|
* eg. for update user action the password will be set to the original password
|
||||||
|
* for create user and change password actions the password is the user input
|
||||||
|
*
|
||||||
|
* @param usedPassword the password to be set
|
||||||
|
* @param user the target
|
||||||
|
*/
|
||||||
|
@AfterMapping
|
||||||
|
void overridePassword(@MappingTarget User user, @Context String usedPassword) {
|
||||||
|
user.setPassword(usedPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package sonia.scm.api.v2.resources;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
|
import org.apache.shiro.authc.credential.PasswordService;
|
||||||
import sonia.scm.ConcurrentModificationException;
|
import sonia.scm.ConcurrentModificationException;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
@@ -26,12 +27,16 @@ public class UserResource {
|
|||||||
private final UserToUserDtoMapper userToDtoMapper;
|
private final UserToUserDtoMapper userToDtoMapper;
|
||||||
|
|
||||||
private final IdResourceManagerAdapter<User, UserDto> adapter;
|
private final IdResourceManagerAdapter<User, UserDto> adapter;
|
||||||
|
private final UserManager userManager;
|
||||||
|
private final PasswordService passwordService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UserResource(UserDtoToUserMapper dtoToUserMapper, UserToUserDtoMapper userToDtoMapper, UserManager manager) {
|
public UserResource(UserDtoToUserMapper dtoToUserMapper, UserToUserDtoMapper userToDtoMapper, UserManager manager, PasswordService passwordService) {
|
||||||
this.dtoToUserMapper = dtoToUserMapper;
|
this.dtoToUserMapper = dtoToUserMapper;
|
||||||
this.userToDtoMapper = userToDtoMapper;
|
this.userToDtoMapper = userToDtoMapper;
|
||||||
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
|
this.adapter = new IdResourceManagerAdapter<>(manager, User.class);
|
||||||
|
this.userManager = manager;
|
||||||
|
this.passwordService = passwordService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,7 +45,6 @@ public class UserResource {
|
|||||||
* <strong>Note:</strong> This method requires "user" privilege.
|
* <strong>Note:</strong> This method requires "user" privilege.
|
||||||
*
|
*
|
||||||
* @param id the id/name of the user
|
* @param id the id/name of the user
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("")
|
@Path("")
|
||||||
@@ -63,7 +67,6 @@ public class UserResource {
|
|||||||
* <strong>Note:</strong> This method requires "user" privilege.
|
* <strong>Note:</strong> This method requires "user" privilege.
|
||||||
*
|
*
|
||||||
* @param name the name of the user to delete.
|
* @param name the name of the user to delete.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@DELETE
|
@DELETE
|
||||||
@Path("")
|
@Path("")
|
||||||
@@ -80,6 +83,7 @@ public class UserResource {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifies the given user.
|
* Modifies the given user.
|
||||||
|
* The given Password in the payload will be ignored. To Change Password use the changePassword endpoint
|
||||||
*
|
*
|
||||||
* <strong>Note:</strong> This method requires "user" privilege.
|
* <strong>Note:</strong> This method requires "user" privilege.
|
||||||
*
|
*
|
||||||
@@ -101,4 +105,30 @@ public class UserResource {
|
|||||||
public Response update(@PathParam("id") String name, @Valid UserDto userDto) throws NotFoundException, ConcurrentModificationException {
|
public Response update(@PathParam("id") String name, @Valid UserDto userDto) throws NotFoundException, ConcurrentModificationException {
|
||||||
return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword()));
|
return adapter.update(name, existing -> dtoToUserMapper.map(userDto, existing.getPassword()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Endpoint is for Admin user to modify a user password.
|
||||||
|
* The oldPassword property of the DTO is not needed here. it will be ignored.
|
||||||
|
* The oldPassword property is needed in the MeResources when the actual user change the own password.
|
||||||
|
*
|
||||||
|
* <strong>Note:</strong> This method requires "user:modify" privilege.
|
||||||
|
* @param name name of the user to be modified
|
||||||
|
* @param passwordChangeDto change password object to modify password. the old password is here not required
|
||||||
|
*/
|
||||||
|
@PUT
|
||||||
|
@Path("password")
|
||||||
|
@Consumes(VndMediaType.PASSWORD_CHANGE)
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 204, condition = "update success"),
|
||||||
|
@ResponseCode(code = 400, condition = "Invalid body, e.g. the user type is not xml or the given oldPassword do not match the stored one"),
|
||||||
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"user\" privilege"),
|
||||||
|
@ResponseCode(code = 404, condition = "not found, no user with the specified id/name available"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
|
public Response changePassword(@PathParam("id") String name, @Valid PasswordChangeDto passwordChangeDto) throws NotFoundException, ConcurrentModificationException {
|
||||||
|
return adapter.update(name, user -> user.changePassword(passwordService.encryptPassword(passwordChangeDto.getNewPassword())), userManager.getUserTypeChecker());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import com.google.common.annotations.VisibleForTesting;
|
|||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
import org.mapstruct.AfterMapping;
|
import org.mapstruct.AfterMapping;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
import org.mapstruct.MappingTarget;
|
import org.mapstruct.MappingTarget;
|
||||||
import sonia.scm.api.rest.resources.UserResource;
|
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.user.UserPermissions;
|
import sonia.scm.user.UserPermissions;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -19,19 +20,17 @@ import static de.otto.edison.hal.Links.linkingTo;
|
|||||||
@Mapper
|
@Mapper
|
||||||
public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
|
public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private UserManager userManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Mapping(target = "attributes", ignore = true)
|
||||||
|
@Mapping(target = "password", ignore = true)
|
||||||
|
public abstract UserDto map(User modelObject);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ResourceLinks resourceLinks;
|
private ResourceLinks resourceLinks;
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
void setResourceLinks(ResourceLinks resourceLinks) {
|
|
||||||
this.resourceLinks = resourceLinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterMapping
|
|
||||||
void removePassword(@MappingTarget UserDto target) {
|
|
||||||
target.setPassword(UserResource.DUMMY_PASSWORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterMapping
|
@AfterMapping
|
||||||
protected void appendLinks(User user, @MappingTarget UserDto target) {
|
protected void appendLinks(User user, @MappingTarget UserDto target) {
|
||||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(target.getName()));
|
Links.Builder linksBuilder = linkingTo().self(resourceLinks.user().self(target.getName()));
|
||||||
@@ -41,6 +40,9 @@ public abstract class UserToUserDtoMapper extends BaseMapper<User, UserDto> {
|
|||||||
if (UserPermissions.modify(user).isPermitted()) {
|
if (UserPermissions.modify(user).isPermitted()) {
|
||||||
linksBuilder.single(link("update", resourceLinks.user().update(target.getName())));
|
linksBuilder.single(link("update", resourceLinks.user().update(target.getName())));
|
||||||
}
|
}
|
||||||
|
if (userManager.isTypeDefault(user)) {
|
||||||
|
linksBuilder.single(link("password", resourceLinks.user().passwordChange(target.getName())));
|
||||||
|
}
|
||||||
target.add(linksBuilder.build());
|
target.add(linksBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
|||||||
@InjectMocks
|
@InjectMocks
|
||||||
private BranchToBranchDtoMapperImpl branchToDtoMapper;
|
private BranchToBranchDtoMapperImpl branchToDtoMapper;
|
||||||
|
|
||||||
private ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper;
|
private BranchChangesetCollectionToDtoMapper changesetCollectionToDtoMapper;
|
||||||
|
|
||||||
private BranchRootResource branchRootResource;
|
private BranchRootResource branchRootResource;
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() throws Exception {
|
public void prepareEnvironment() throws Exception {
|
||||||
changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
changesetCollectionToDtoMapper = new BranchChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||||
BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks);
|
BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks);
|
||||||
branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper);
|
branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper);
|
||||||
super.branchRootResource = Providers.of(branchRootResource);
|
super.branchRootResource = Providers.of(branchRootResource);
|
||||||
@@ -152,6 +152,10 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
|||||||
when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder);
|
when(logCommandBuilder.setPagingLimit(anyInt())).thenReturn(logCommandBuilder);
|
||||||
when(logCommandBuilder.setBranch(anyString())).thenReturn(logCommandBuilder);
|
when(logCommandBuilder.setBranch(anyString())).thenReturn(logCommandBuilder);
|
||||||
when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult);
|
when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult);
|
||||||
|
Branches branches = mock(Branches.class);
|
||||||
|
List<Branch> branchList = Lists.newArrayList(new Branch("master",id));
|
||||||
|
when(branches.getBranches()).thenReturn(branchList);
|
||||||
|
when(branchesCommandBuilder.getBranches()).thenReturn(branches);
|
||||||
MockHttpRequest request = MockHttpRequest.get(BRANCH_URL + "/changesets/");
|
MockHttpRequest request = MockHttpRequest.get(BRANCH_URL + "/changesets/");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
@@ -161,6 +165,5 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
|||||||
assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", authorName)));
|
assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", authorName)));
|
||||||
assertTrue(response.getContentAsString().contains(String.format("\"mail\":\"%s\"", authorEmail)));
|
assertTrue(response.getContentAsString().contains(String.format("\"mail\":\"%s\"", authorEmail)));
|
||||||
assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit)));
|
assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit)));
|
||||||
assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public class DispatcherMock {
|
|||||||
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
|
||||||
dispatcher.getProviderFactory().registerProvider(ConcurrentModificationExceptionMapper.class);
|
dispatcher.getProviderFactory().registerProvider(ConcurrentModificationExceptionMapper.class);
|
||||||
dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class);
|
dispatcher.getProviderFactory().registerProvider(InternalRepositoryExceptionMapper.class);
|
||||||
|
dispatcher.getProviderFactory().registerProvider(ChangePasswordNotAllowedExceptionMapper.class);
|
||||||
|
dispatcher.getProviderFactory().registerProvider(InvalidPasswordExceptionMapper.class);
|
||||||
return dispatcher;
|
return dispatcher;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package sonia.scm.api.v2.resources;
|
|||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
|
import org.apache.shiro.authc.credential.PasswordService;
|
||||||
import org.jboss.resteasy.core.Dispatcher;
|
import org.jboss.resteasy.core.Dispatcher;
|
||||||
import org.jboss.resteasy.mock.MockDispatcherFactory;
|
|
||||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
@@ -22,11 +23,17 @@ import java.net.URISyntaxException;
|
|||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.mockito.MockitoAnnotations.initMocks;
|
import static org.mockito.MockitoAnnotations.initMocks;
|
||||||
|
import static sonia.scm.api.v2.resources.DispatcherMock.createDispatcher;
|
||||||
|
|
||||||
@SubjectAware(
|
@SubjectAware(
|
||||||
|
username = "trillian",
|
||||||
|
password = "secret",
|
||||||
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||||
)
|
)
|
||||||
public class MeResourceTest {
|
public class MeResourceTest {
|
||||||
@@ -34,8 +41,7 @@ public class MeResourceTest {
|
|||||||
@Rule
|
@Rule
|
||||||
public ShiroRule shiro = new ShiroRule();
|
public ShiroRule shiro = new ShiroRule();
|
||||||
|
|
||||||
private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
private Dispatcher dispatcher;
|
||||||
|
|
||||||
|
|
||||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/"));
|
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(URI.create("/"));
|
||||||
@Mock
|
@Mock
|
||||||
@@ -47,22 +53,28 @@ public class MeResourceTest {
|
|||||||
private UserManager userManager;
|
private UserManager userManager;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private UserToUserDtoMapperImpl userToDtoMapper;
|
private MeToUserDtoMapperImpl userToDtoMapper;
|
||||||
|
|
||||||
private ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
private ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PasswordService passwordService;
|
||||||
|
private User originalUser;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() throws Exception {
|
public void prepareEnvironment() throws Exception {
|
||||||
initMocks(this);
|
initMocks(this);
|
||||||
createDummyUser("trillian");
|
originalUser = createDummyUser("trillian");
|
||||||
when(userManager.create(userCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]);
|
when(userManager.create(userCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]);
|
||||||
doNothing().when(userManager).modify(userCaptor.capture());
|
doNothing().when(userManager).modify(userCaptor.capture());
|
||||||
doNothing().when(userManager).delete(userCaptor.capture());
|
doNothing().when(userManager).delete(userCaptor.capture());
|
||||||
userToDtoMapper.setResourceLinks(resourceLinks);
|
when(userManager.isTypeDefault(userCaptor.capture())).thenCallRealMethod();
|
||||||
MeResource meResource = new MeResource(userToDtoMapper, userManager);
|
when(userManager.getUserTypeChecker()).thenCallRealMethod();
|
||||||
dispatcher.getRegistry().addSingletonResource(meResource);
|
when(userManager.getDefaultType()).thenReturn("xml");
|
||||||
|
MeResource meResource = new MeResource(userToDtoMapper, userManager, passwordService);
|
||||||
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/"));
|
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/"));
|
||||||
when(scmPathInfoStore.get()).thenReturn(uriInfo);
|
when(scmPathInfoStore.get()).thenReturn(uriInfo);
|
||||||
|
dispatcher = createDispatcher(meResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -76,14 +88,77 @@ public class MeResourceTest {
|
|||||||
|
|
||||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
assertTrue(response.getContentAsString().contains("\"name\":\"trillian\""));
|
assertTrue(response.getContentAsString().contains("\"name\":\"trillian\""));
|
||||||
assertTrue(response.getContentAsString().contains("\"password\":\"__dummypassword__\""));
|
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/me/\"}"));
|
||||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/trillian\"}"));
|
|
||||||
assertTrue(response.getContentAsString().contains("\"delete\":{\"href\":\"/v2/users/trillian\"}"));
|
assertTrue(response.getContentAsString().contains("\"delete\":{\"href\":\"/v2/users/trillian\"}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SubjectAware(username = "trillian", password = "secret")
|
||||||
|
public void shouldEncryptPasswordBeforeChanging() throws Exception {
|
||||||
|
String newPassword = "pwd123";
|
||||||
|
String encryptedNewPassword = "encrypted123";
|
||||||
|
String oldPassword = "notEncriptedSecret";
|
||||||
|
String content = String.format("{ \"oldPassword\": \"%s\" , \"newPassword\": \"%s\" }", oldPassword, newPassword);
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.put("/" + MeResource.ME_PATH_V2 + "password")
|
||||||
|
.contentType(VndMediaType.PASSWORD_CHANGE)
|
||||||
|
.content(content.getBytes());
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
when(passwordService.encryptPassword(eq(newPassword))).thenReturn(encryptedNewPassword);
|
||||||
|
when(passwordService.encryptPassword(eq(oldPassword))).thenReturn("secret");
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||||
|
verify(userManager).modify(any(User.class));
|
||||||
|
User updatedUser = userCaptor.getValue();
|
||||||
|
assertEquals(encryptedNewPassword, updatedUser.getPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SubjectAware(username = "trillian", password = "secret")
|
||||||
|
public void shouldGet400OnChangePasswordOfUserWithNonDefaultType() throws Exception {
|
||||||
|
originalUser.setType("not an xml type");
|
||||||
|
String newPassword = "pwd123";
|
||||||
|
String oldPassword = "notEncriptedSecret";
|
||||||
|
String content = String.format("{ \"oldPassword\": \"%s\" , \"newPassword\": \"%s\" }", oldPassword, newPassword);
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.put("/" + MeResource.ME_PATH_V2 + "password")
|
||||||
|
.contentType(VndMediaType.PASSWORD_CHANGE)
|
||||||
|
.content(content.getBytes());
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
when(passwordService.encryptPassword(newPassword)).thenReturn("encrypted123");
|
||||||
|
when(passwordService.encryptPassword(eq(oldPassword))).thenReturn("secret");
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SubjectAware(username = "trillian", password = "secret")
|
||||||
|
public void shouldGet400OnChangePasswordIfOldPasswordDoesNotMatchOriginalPassword() throws Exception {
|
||||||
|
String newPassword = "pwd123";
|
||||||
|
String oldPassword = "notEncriptedSecret";
|
||||||
|
String content = String.format("{ \"oldPassword\": \"%s\" , \"newPassword\": \"%s\" }", oldPassword, newPassword);
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.put("/" + MeResource.ME_PATH_V2 + "password")
|
||||||
|
.contentType(VndMediaType.PASSWORD_CHANGE)
|
||||||
|
.content(content.getBytes());
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
when(passwordService.encryptPassword(newPassword)).thenReturn("encrypted123");
|
||||||
|
when(passwordService.encryptPassword(eq(oldPassword))).thenReturn("differentThanSecret");
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private User createDummyUser(String name) {
|
private User createDummyUser(String name) {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setName(name);
|
user.setName(name);
|
||||||
|
user.setType("xml");
|
||||||
user.setPassword("secret");
|
user.setPassword("secret");
|
||||||
user.setCreationDate(System.currentTimeMillis());
|
user.setCreationDate(System.currentTimeMillis());
|
||||||
when(userManager.get(name)).thenReturn(user);
|
when(userManager.get(name)).thenReturn(user);
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.subject.support.SubjectThreadState;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.apache.shiro.util.ThreadState;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
import sonia.scm.user.UserManager;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.mockito.MockitoAnnotations.initMocks;
|
||||||
|
|
||||||
|
public class MeToUserDtoMapperTest {
|
||||||
|
|
||||||
|
private final URI baseUri = URI.create("http://example.com/base/");
|
||||||
|
@SuppressWarnings("unused") // Is injected
|
||||||
|
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private UserManager userManager;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private MeToUserDtoMapperImpl mapper;
|
||||||
|
|
||||||
|
private final Subject subject = mock(Subject.class);
|
||||||
|
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||||
|
|
||||||
|
private URI expectedBaseUri;
|
||||||
|
private URI expectedUserBaseUri;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
initMocks(this);
|
||||||
|
when(userManager.getDefaultType()).thenReturn("xml");
|
||||||
|
expectedBaseUri = baseUri.resolve(MeResource.ME_PATH_V2 + "/");
|
||||||
|
expectedUserBaseUri = baseUri.resolve(UserRootResource.USERS_PATH_V2 + "/");
|
||||||
|
subjectThreadState.bind();
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void unbindSubject() {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldMapTheUpdateLink() {
|
||||||
|
User user = createDefaultUser();
|
||||||
|
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
||||||
|
|
||||||
|
UserDto userDto = mapper.map(user);
|
||||||
|
assertEquals("expected update link", expectedUserBaseUri.resolve("abc").toString(), userDto.getLinks().getLinkBy("update").get().getHref());
|
||||||
|
|
||||||
|
when(subject.isPermitted("user:modify:abc")).thenReturn(false);
|
||||||
|
userDto = mapper.map(user);
|
||||||
|
assertFalse("expected no update link", userDto.getLinks().getLinkBy("update").isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldMapTheSelfLink() {
|
||||||
|
User user = createDefaultUser();
|
||||||
|
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
||||||
|
|
||||||
|
UserDto userDto = mapper.map(user);
|
||||||
|
assertEquals("expected self link", expectedBaseUri.toString(), userDto.getLinks().getLinkBy("self").get().getHref());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldMapTheDeleteLink() {
|
||||||
|
User user = createDefaultUser();
|
||||||
|
when(subject.isPermitted("user:delete:abc")).thenReturn(true);
|
||||||
|
|
||||||
|
UserDto userDto = mapper.map(user);
|
||||||
|
assertEquals("expected update link", expectedUserBaseUri.resolve("abc").toString(), userDto.getLinks().getLinkBy("delete").get().getHref());
|
||||||
|
|
||||||
|
when(subject.isPermitted("user:delete:abc")).thenReturn(false);
|
||||||
|
userDto = mapper.map(user);
|
||||||
|
assertFalse("expected no delete link", userDto.getLinks().getLinkBy("delete").isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGetPasswordLinkOnlyForDefaultUserType() {
|
||||||
|
User user = createDefaultUser();
|
||||||
|
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
||||||
|
when(userManager.isTypeDefault(eq(user))).thenReturn(true);
|
||||||
|
|
||||||
|
UserDto userDto = mapper.map(user);
|
||||||
|
|
||||||
|
assertEquals("expected password link with modify permission", expectedBaseUri.resolve("password").toString(), userDto.getLinks().getLinkBy("password").get().getHref());
|
||||||
|
|
||||||
|
when(subject.isPermitted("user:modify:abc")).thenReturn(false);
|
||||||
|
userDto = mapper.map(user);
|
||||||
|
assertEquals("expected password link on mission modify permission", expectedBaseUri.resolve("password").toString(), userDto.getLinks().getLinkBy("password").get().getHref());
|
||||||
|
|
||||||
|
when(userManager.isTypeDefault(eq(user))).thenReturn(false);
|
||||||
|
|
||||||
|
userDto = mapper.map(user);
|
||||||
|
|
||||||
|
assertFalse("expected no password link", userDto.getLinks().getLinkBy("password").isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGetEmptyPasswordProperty() {
|
||||||
|
User user = createDefaultUser();
|
||||||
|
user.setPassword("myHighSecurePassword");
|
||||||
|
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
||||||
|
|
||||||
|
UserDto userDto = mapper.map(user);
|
||||||
|
|
||||||
|
assertThat(userDto.getPassword()).as("hide password for the me resource").isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
private User createDefaultUser() {
|
||||||
|
User user = new User();
|
||||||
|
user.setName("abc");
|
||||||
|
user.setCreationDate(1L);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -12,7 +12,9 @@ public class ResourceLinksMock {
|
|||||||
ScmPathInfo uriInfo = mock(ScmPathInfo.class);
|
ScmPathInfo uriInfo = mock(ScmPathInfo.class);
|
||||||
when(uriInfo.getApiRestUri()).thenReturn(baseUri);
|
when(uriInfo.getApiRestUri()).thenReturn(baseUri);
|
||||||
|
|
||||||
when(resourceLinks.user()).thenReturn(new ResourceLinks.UserLinks(uriInfo));
|
ResourceLinks.UserLinks userLinks = new ResourceLinks.UserLinks(uriInfo);
|
||||||
|
when(resourceLinks.user()).thenReturn(userLinks);
|
||||||
|
when(resourceLinks.me()).thenReturn(new ResourceLinks.MeLinks(uriInfo,userLinks));
|
||||||
when(resourceLinks.userCollection()).thenReturn(new ResourceLinks.UserCollectionLinks(uriInfo));
|
when(resourceLinks.userCollection()).thenReturn(new ResourceLinks.UserCollectionLinks(uriInfo));
|
||||||
when(resourceLinks.group()).thenReturn(new ResourceLinks.GroupLinks(uriInfo));
|
when(resourceLinks.group()).thenReturn(new ResourceLinks.GroupLinks(uriInfo));
|
||||||
when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(uriInfo));
|
when(resourceLinks.groupCollection()).thenReturn(new ResourceLinks.GroupCollectionLinks(uriInfo));
|
||||||
|
|||||||
@@ -23,18 +23,9 @@ public class UserDtoToUserMapperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldMapFields() {
|
public void shouldMapFields() {
|
||||||
UserDto dto = createDefaultDto();
|
UserDto dto = createDefaultDto();
|
||||||
User user = mapper.map(dto, "original password");
|
User user = mapper.map(dto, "used password");
|
||||||
assertEquals("abc" , user.getName());
|
assertEquals("abc" , user.getName());
|
||||||
}
|
assertEquals("used password" , user.getPassword());
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldEncodePassword() {
|
|
||||||
when(passwordService.encryptPassword("unencrypted")).thenReturn("encrypted");
|
|
||||||
|
|
||||||
UserDto dto = createDefaultDto();
|
|
||||||
dto.setPassword("unencrypted");
|
|
||||||
User user = mapper.map(dto, "original password");
|
|
||||||
assertEquals("encrypted" , user.getPassword());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -61,19 +62,23 @@ public class UserRootResourceTest {
|
|||||||
private UserToUserDtoMapperImpl userToDtoMapper;
|
private UserToUserDtoMapperImpl userToDtoMapper;
|
||||||
|
|
||||||
private ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
private ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
||||||
|
private User originalUser;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() throws Exception {
|
public void prepareEnvironment() throws Exception {
|
||||||
initMocks(this);
|
initMocks(this);
|
||||||
User dummyUser = createDummyUser("Neo");
|
originalUser = createDummyUser("Neo");
|
||||||
when(userManager.create(userCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]);
|
when(userManager.create(userCaptor.capture())).thenAnswer(invocation -> invocation.getArguments()[0]);
|
||||||
|
when(userManager.isTypeDefault(userCaptor.capture())).thenCallRealMethod();
|
||||||
|
when(userManager.getUserTypeChecker()).thenCallRealMethod();
|
||||||
doNothing().when(userManager).modify(userCaptor.capture());
|
doNothing().when(userManager).modify(userCaptor.capture());
|
||||||
doNothing().when(userManager).delete(userCaptor.capture());
|
doNothing().when(userManager).delete(userCaptor.capture());
|
||||||
|
when(userManager.getDefaultType()).thenReturn("xml");
|
||||||
|
|
||||||
UserCollectionToDtoMapper userCollectionToDtoMapper = new UserCollectionToDtoMapper(userToDtoMapper, resourceLinks);
|
UserCollectionToDtoMapper userCollectionToDtoMapper = new UserCollectionToDtoMapper(userToDtoMapper, resourceLinks);
|
||||||
UserCollectionResource userCollectionResource = new UserCollectionResource(userManager, dtoToUserMapper,
|
UserCollectionResource userCollectionResource = new UserCollectionResource(userManager, dtoToUserMapper,
|
||||||
userCollectionToDtoMapper, resourceLinks);
|
userCollectionToDtoMapper, resourceLinks, passwordService);
|
||||||
UserResource userResource = new UserResource(dtoToUserMapper, userToDtoMapper, userManager);
|
UserResource userResource = new UserResource(dtoToUserMapper, userToDtoMapper, userManager, passwordService);
|
||||||
UserRootResource userRootResource = new UserRootResource(Providers.of(userCollectionResource),
|
UserRootResource userRootResource = new UserRootResource(Providers.of(userCollectionResource),
|
||||||
Providers.of(userResource));
|
Providers.of(userResource));
|
||||||
|
|
||||||
@@ -89,7 +94,6 @@ public class UserRootResourceTest {
|
|||||||
|
|
||||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
assertTrue(response.getContentAsString().contains("\"name\":\"Neo\""));
|
assertTrue(response.getContentAsString().contains("\"name\":\"Neo\""));
|
||||||
assertTrue(response.getContentAsString().contains("\"password\":\"__dummypassword__\""));
|
|
||||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/Neo\"}"));
|
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/Neo\"}"));
|
||||||
assertTrue(response.getContentAsString().contains("\"delete\":{\"href\":\"/v2/users/Neo\"}"));
|
assertTrue(response.getContentAsString().contains("\"delete\":{\"href\":\"/v2/users/Neo\"}"));
|
||||||
}
|
}
|
||||||
@@ -104,13 +108,48 @@ public class UserRootResourceTest {
|
|||||||
|
|
||||||
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
assertTrue(response.getContentAsString().contains("\"name\":\"Neo\""));
|
assertTrue(response.getContentAsString().contains("\"name\":\"Neo\""));
|
||||||
assertTrue(response.getContentAsString().contains("\"password\":\"__dummypassword__\""));
|
|
||||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/Neo\"}"));
|
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/users/Neo\"}"));
|
||||||
assertFalse(response.getContentAsString().contains("\"delete\":{\"href\":\"/v2/users/Neo\"}"));
|
assertFalse(response.getContentAsString().contains("\"delete\":{\"href\":\"/v2/users/Neo\"}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldCreateNewUserWithEncryptedPassword() throws Exception {
|
public void shouldEncryptPasswordBeforeChanging() throws Exception {
|
||||||
|
String newPassword = "pwd123";
|
||||||
|
String content = String.format("{\"newPassword\": \"%s\"}", newPassword);
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.put("/" + UserRootResource.USERS_PATH_V2 + "Neo/password")
|
||||||
|
.contentType(VndMediaType.PASSWORD_CHANGE)
|
||||||
|
.content(content.getBytes());
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
when(passwordService.encryptPassword(newPassword)).thenReturn("encrypted123");
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||||
|
verify(userManager).modify(any(User.class));
|
||||||
|
User updatedUser = userCaptor.getValue();
|
||||||
|
assertEquals("encrypted123", updatedUser.getPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGet400OnChangePasswordOfUserWithNonDefaultType() throws Exception {
|
||||||
|
originalUser.setType("not an xml type");
|
||||||
|
String newPassword = "pwd123";
|
||||||
|
String content = String.format("{\"newPassword\": \"%s\"}", newPassword);
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.put("/" + UserRootResource.USERS_PATH_V2 + "Neo/password")
|
||||||
|
.contentType(VndMediaType.PASSWORD_CHANGE)
|
||||||
|
.content(content.getBytes());
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
when(passwordService.encryptPassword(newPassword)).thenReturn("encrypted123");
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldEncryptPasswordBeforeCreatingUser() throws Exception {
|
||||||
URL url = Resources.getResource("sonia/scm/api/v2/user-test-create.json");
|
URL url = Resources.getResource("sonia/scm/api/v2/user-test-create.json");
|
||||||
byte[] userJson = Resources.toByteArray(url);
|
byte[] userJson = Resources.toByteArray(url);
|
||||||
|
|
||||||
@@ -130,7 +169,7 @@ public class UserRootResourceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldUpdateChangedUserWithEncryptedPassword() throws Exception {
|
public void shouldIgnoreGivenPasswordOnUpdatingUser() throws Exception {
|
||||||
URL url = Resources.getResource("sonia/scm/api/v2/user-test-update.json");
|
URL url = Resources.getResource("sonia/scm/api/v2/user-test-update.json");
|
||||||
byte[] userJson = Resources.toByteArray(url);
|
byte[] userJson = Resources.toByteArray(url);
|
||||||
|
|
||||||
@@ -139,14 +178,13 @@ public class UserRootResourceTest {
|
|||||||
.contentType(VndMediaType.USER)
|
.contentType(VndMediaType.USER)
|
||||||
.content(userJson);
|
.content(userJson);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
when(passwordService.encryptPassword("pwd123")).thenReturn("encrypted123");
|
|
||||||
|
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus());
|
||||||
verify(userManager).modify(any(User.class));
|
verify(userManager).modify(any(User.class));
|
||||||
User updatedUser = userCaptor.getValue();
|
User updatedUser = userCaptor.getValue();
|
||||||
assertEquals("encrypted123", updatedUser.getPassword());
|
assertEquals(originalUser.getPassword(), updatedUser.getPassword());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -154,7 +192,7 @@ public class UserRootResourceTest {
|
|||||||
MockHttpRequest request = MockHttpRequest
|
MockHttpRequest request = MockHttpRequest
|
||||||
.post("/" + UserRootResource.USERS_PATH_V2)
|
.post("/" + UserRootResource.USERS_PATH_V2)
|
||||||
.contentType(VndMediaType.USER)
|
.contentType(VndMediaType.USER)
|
||||||
.content(new byte[] {});
|
.content(new byte[]{});
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
when(passwordService.encryptPassword("pwd123")).thenReturn("encrypted123");
|
when(passwordService.encryptPassword("pwd123")).thenReturn("encrypted123");
|
||||||
|
|
||||||
@@ -265,6 +303,7 @@ public class UserRootResourceTest {
|
|||||||
private User createDummyUser(String name) {
|
private User createDummyUser(String name) {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setName(name);
|
user.setName(name);
|
||||||
|
user.setType("xml");
|
||||||
user.setPassword("redpill");
|
user.setPassword("redpill");
|
||||||
user.setCreationDate(System.currentTimeMillis());
|
user.setCreationDate(System.currentTimeMillis());
|
||||||
when(userManager.get(name)).thenReturn(user);
|
when(userManager.get(name)).thenReturn(user);
|
||||||
|
|||||||
@@ -8,14 +8,17 @@ import org.junit.After;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import sonia.scm.api.rest.resources.UserResource;
|
import org.mockito.Mock;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
|
import sonia.scm.user.UserManager;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.mockito.MockitoAnnotations.initMocks;
|
import static org.mockito.MockitoAnnotations.initMocks;
|
||||||
@@ -26,6 +29,9 @@ public class UserToUserDtoMapperTest {
|
|||||||
@SuppressWarnings("unused") // Is injected
|
@SuppressWarnings("unused") // Is injected
|
||||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private UserManager userManager;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private UserToUserDtoMapperImpl mapper;
|
private UserToUserDtoMapperImpl mapper;
|
||||||
|
|
||||||
@@ -37,6 +43,7 @@ public class UserToUserDtoMapperTest {
|
|||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
initMocks(this);
|
initMocks(this);
|
||||||
|
when(userManager.getDefaultType()).thenReturn("xml");
|
||||||
expectedBaseUri = baseUri.resolve(UserRootResource.USERS_PATH_V2 + "/");
|
expectedBaseUri = baseUri.resolve(UserRootResource.USERS_PATH_V2 + "/");
|
||||||
subjectThreadState.bind();
|
subjectThreadState.bind();
|
||||||
ThreadContext.bind(subject);
|
ThreadContext.bind(subject);
|
||||||
@@ -53,11 +60,42 @@ public class UserToUserDtoMapperTest {
|
|||||||
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
||||||
|
|
||||||
UserDto userDto = mapper.map(user);
|
UserDto userDto = mapper.map(user);
|
||||||
|
|
||||||
assertEquals("expected self link", expectedBaseUri.resolve("abc").toString(), userDto.getLinks().getLinkBy("self").get().getHref());
|
assertEquals("expected self link", expectedBaseUri.resolve("abc").toString(), userDto.getLinks().getLinkBy("self").get().getHref());
|
||||||
assertEquals("expected update link", expectedBaseUri.resolve("abc").toString(), userDto.getLinks().getLinkBy("update").get().getHref());
|
assertEquals("expected update link", expectedBaseUri.resolve("abc").toString(), userDto.getLinks().getLinkBy("update").get().getHref());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGetPasswordLinkOnlyForDefaultUserType() {
|
||||||
|
User user = createDefaultUser();
|
||||||
|
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
||||||
|
when(userManager.isTypeDefault(eq(user))).thenReturn(true);
|
||||||
|
|
||||||
|
UserDto userDto = mapper.map(user);
|
||||||
|
|
||||||
|
assertEquals("expected password link with modify permission", expectedBaseUri.resolve("abc/password").toString(), userDto.getLinks().getLinkBy("password").get().getHref());
|
||||||
|
|
||||||
|
when(subject.isPermitted("user:modify:abc")).thenReturn(false);
|
||||||
|
userDto = mapper.map(user);
|
||||||
|
assertEquals("expected password link on mission modify permission", expectedBaseUri.resolve("abc/password").toString(), userDto.getLinks().getLinkBy("password").get().getHref());
|
||||||
|
|
||||||
|
when(userManager.isTypeDefault(eq(user))).thenReturn(false);
|
||||||
|
|
||||||
|
userDto = mapper.map(user);
|
||||||
|
|
||||||
|
assertFalse("expected no password link", userDto.getLinks().getLinkBy("password").isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldGetEmptyPasswordProperty() {
|
||||||
|
User user = createDefaultUser();
|
||||||
|
user.setPassword("myHighSecurePassword");
|
||||||
|
when(subject.isPermitted("user:modify:abc")).thenReturn(true);
|
||||||
|
|
||||||
|
UserDto userDto = mapper.map(user);
|
||||||
|
|
||||||
|
assertThat(userDto.getPassword()).isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldMapLinks_forDelete() {
|
public void shouldMapLinks_forDelete() {
|
||||||
User user = createDefaultUser();
|
User user = createDefaultUser();
|
||||||
@@ -97,16 +135,6 @@ public class UserToUserDtoMapperTest {
|
|||||||
assertEquals("abc", userDto.getName());
|
assertEquals("abc", userDto.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldRemovePassword() {
|
|
||||||
User user = createDefaultUser();
|
|
||||||
user.setPassword("password");
|
|
||||||
|
|
||||||
UserDto userDto = mapper.map(user);
|
|
||||||
|
|
||||||
assertEquals(UserResource.DUMMY_PASSWORT, userDto.getPassword());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldMapTimes() {
|
public void shouldMapTimes() {
|
||||||
User user = createDefaultUser();
|
User user = createDefaultUser();
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import com.github.sdorra.shiro.SubjectAware;
|
|||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import org.apache.shiro.authz.UnauthorizedException;
|
import org.apache.shiro.authz.UnauthorizedException;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
@@ -94,6 +95,10 @@ import static org.mockito.Mockito.when;
|
|||||||
)
|
)
|
||||||
public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||||
|
|
||||||
|
{
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ShiroRule shiro = new ShiroRule();
|
public ShiroRule shiro = new ShiroRule();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user