mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 01:15:44 +01:00
merge with branch 1.x
This commit is contained in:
76
docs/mercurial/clone-empty.md
Normal file
76
docs/mercurial/clone-empty.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Clone empty repository
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1efk0qxy1dj5v133hev91zwsf4;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 130.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
.
|
||||||
|
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1rsxj8u1rq9wizawhyyxok2p5;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: cmds=heads+%3Bknown+nodes%3D.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=ewyx4m53d8dajjsob6gxobne;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 42.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
0000000000000000000000000000000000000000
|
||||||
|
;
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1o0hou15jtiywsywutf30qwm8;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 05:57:18 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
.
|
||||||
|
publishing.True
|
||||||
|
```
|
||||||
117
docs/mercurial/push-bookmark.md
Normal file
117
docs/mercurial/push-bookmark.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# Push bookmark
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=7rq9vpp9svfm1sicq7h9vetmv;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 130.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
T 172.17.0.2:8080 -> 172.17.0.1:36576 [AP]
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1553csz4sf7scyvw8mqnqfirn;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 43.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
ef5993bb4abb32a0565c347844c6d939fc4f4b98
|
||||||
|
;1
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=11xa5u3nrmx8k1nar3sazg6jzh;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1p1uzcvfe1pvzh2buzo658rxw;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1mhlj3ucfzdp6ifmzoua4zwit;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: key=markone&namespace=bookmarks&new=ef5993bb4abb32a0565c347844c6d939fc4f4b98&old=.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 0.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=s4vtagb303dv1xg809wnp7e8z;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 08:08:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 2.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
.
|
||||||
|
1
|
||||||
|
```
|
||||||
167
docs/mercurial/push-multiple-branches-to-new.md
Normal file
167
docs/mercurial/push-multiple-branches-to-new.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# Push multiple branches to new repository
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1wu06ykfd4bcv1uv731y4hss2m;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 130.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1rajglvqx222g5nppcq3jdfk0;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 43.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
0000000000000000000000000000000000000000
|
||||||
|
;0
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=known HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: nodes=c0ceccb3b2f0f5c977ff32b9337519e5f37942c2+187ddf37e237c370514487a0bb1a226f11a780b3+b5914611f84eae14543684b2721eec88b0edac12+8b63a323606f10c86b30465570c2574eb7a3a989.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=a5vykp1f0ga2186l8v3gu6lid;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 4.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
0000
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=s8lpwqm4c2nqs9kwcg2ca6vm;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1d2qj3kynxlhvk31oli4kk7vf;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=unbundle HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: heads=686173686564+6768033e216468247bd031a0a2d9876d79818f8f.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 913.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HG10GZx...oh.U......E.1.....2q.<...s.1.YK*e#..b..{....{..%A.....
|
||||||
|
,\.....Y.XV....Q/J......`Q/.z.{...<.7....r.s.~?.?..<o....O.]....?.}..m?]z..I..u..}.a..rg..R[i.,D ...!.1..h.r.....G...M.\...J[.....+{.k...u..bL.!....F('..=Q.'......W.>5.~`..?..........O.j.0.....Ih.....!@.P... ..a
|
||||||
|
;!y..cT...]q.8Zg=...<..,.tq.*.........l........';..w^...w...-......Co..Fs.HYg...
|
||||||
|
9.F#.P......1..;......D.H.9$@.^....r:E..18...H....3..h...-.=.6l......=q .)."Yg..p\...s@.#.H.*....c8&96..2.GjJ.`.J....r...=Q1..@R.3.o{q...|.......yq.k..,cY..:[... ...S.2...VYp..c5..&.SFR.............V.d..o..........,.. A..M....k...0_.LO1..1"4.;...B....5.9.".U.m.e......]\../p..;?C..<vW.....|......F.8,....s....2.T
|
||||||
|
N. .k..>W9.........n.~o..gW...Q;..$....S..X.CN.5I].H..!.@...U..J...L.lY.../.-...6.:.Q.'...>.e'..<#3........OL}.52ra[..g*Y:Y....w...=..Z\...S.......tz..;..mf...W......&yUN.r.......4...........`..F...nT..U9................_.~..?...BwzUN.r....B.
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=163487i0ayf9s1k2ng9e1azadj;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 102.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
1
|
||||||
|
adding changesets
|
||||||
|
adding manifests
|
||||||
|
adding file changes
|
||||||
|
added 5 changesets with 3 changes to 3 files
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=a3i712yjss6t1xsxltnssq0tl;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 58.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
c0ceccb3b2f0f5c977ff32b9337519e5f37942c2.1
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: key=ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 0.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=g8cavdze42d83knmuasrlg10;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 07:55:14 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 2.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
.
|
||||||
|
1
|
||||||
|
```
|
||||||
183
docs/mercurial/push-multiple-branches.md
Normal file
183
docs/mercurial/push-multiple-branches.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# Push multiple branches
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1mvm1rxg8333iib7754ksusxc;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 130.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=58p9y9vcnz5cjs22dtw8mpwk;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 43.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
c0ceccb3b2f0f5c977ff32b9337519e5f37942c2
|
||||||
|
;0
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=v5wfwj8k4t261dp6808cdouoa;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=3pgqytfhm4za1dco9p41j9yz5;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=branchmap HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
.
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1tiz6zf7ui54e1j3d4vouxig5m;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 48.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
default c0ceccb3b2f0f5c977ff32b9337519e5f37942c2
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1augu4tc71xax1dit20dtxzkez;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=unbundle HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: heads=686173686564+95373ca7cd5371cb6c49bb755ee451d9ec585845.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 746.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HG10GZx...]H.Q...z..r.,.Y..Bw.~..c.Z&...hf.:......e.XK.X,...
|
||||||
|
,2.E1.B+...(.B"."*..z1.*......M...........93..k|..I..<...h..J_.L.9>.h..@.....op..^.....#....;.*..W....T@....!..dY....jT..A0O6.}..S.2..JPU.O6...aa...rY.VOf9.....7Ukj.&..<...z...j......%}..Jc.8c....k.."9.&".I.P.\..$.At......0..1..g.2.)<..$.. E..dn#....#.Y$3...n...5....J.e.......SNHN.q.MD..4..."I..`PF..?GH1..F..uES..Rl$47.....a........D.1...87.k.t..D..O_.3..6'cN.w.M..|@E.).X!.h*....U.B.X.....h..$.`4...
|
||||||
|
-..O.:./..oWN.....3...x.L......_[..../..k.R$.x.2..kkv.\2R....4...@.2...1Q..T
|
||||||
|
..(..m....s.Uo.......{.d.....Y....TYO...S.Pl`a5. ."N$.@...b...qJ.l.).n...1..F.Zy.....&>v;.q.....Jy..X.?.;....>U..|.....d.Y.*.q...NR.3...h.T..x..,.]...p{.^S.S...~..`..q.\j{.oCI.............K.....l9n.s......
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1e4fnqpncil9z1f7a2pya26nt7;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 102.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
1
|
||||||
|
adding changesets
|
||||||
|
adding manifests
|
||||||
|
adding file changes
|
||||||
|
added 4 changesets with 2 changes to 2 files
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=f9hvrjssniym1qe33q0u8r2m8;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 101.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
b5914611f84eae14543684b2721eec88b0edac12.1
|
||||||
|
187ddf37e237c370514487a0bb1a226f11a780b3.1
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: key=ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 0.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=z5lrut6940a650sw6x9bls8a;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:16:50 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 2.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
1
|
||||||
|
```
|
||||||
147
docs/mercurial/push-single-changeset.md
Normal file
147
docs/mercurial/push-single-changeset.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# Push single changeset
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /scm/hg/hgtest?cmd=capabilities HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=18r2i2jsba46d14ncsmcjdhaem;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 130.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=batch HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: cmds=heads+%3Bknown+nodes%3Dc0ceccb3b2f0f5c977ff32b9337519e5f37942c2.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=1fw0i0c5zpy281gfgha0f26git;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 43.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
0000000000000000000000000000000000000000
|
||||||
|
;0
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=dfa46uaqgf39w3jhk857oymu;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 15.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=bookmarks.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=2sk1llvrsagg33xgmwyirfpi;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 0.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=unbundle HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: heads=686173686564+6768033e216468247bd031a0a2d9876d79818f8f.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 261.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HG10GZx.c``8w.....>|=Y..h.q.....N.......%......Z....&&&.&...YZ.&.&[$.........$.%q..&%..d&.).....%*.....Y.....9z...v\..FF......
|
||||||
|
..F..\.z%.%\\.)).)
|
||||||
|
.P[....D..[un..L).nc..q.m*.H.l#C...eZJ..YJ.Q.qR...e.aJ.EjjJ.AZ..A.Q..E.1.T.'D..C....7s.}..4G........3.S.mL.0.....zk
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=hlucs5utn1ifnpehqmjpt593;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 102.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
1
|
||||||
|
adding changesets
|
||||||
|
adding manifests
|
||||||
|
adding file changes
|
||||||
|
added 1 changesets with 1 changes to 1 files
|
||||||
|
|
||||||
|
T 172.17.0.1:33206 -> 172.17.0.2:8080 [AP]
|
||||||
|
GET /scm/hg/hgtest?cmd=listkeys HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: namespace=phases.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=15xomlrxl8qja1cj47rjpqda0y;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 58.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
c0ceccb3b2f0f5c977ff32b9337519e5f37942c2.1
|
||||||
|
publishing.True
|
||||||
|
|
||||||
|
POST /scm/hg/hgtest?cmd=pushkey HTTP/1.1.
|
||||||
|
Accept-Encoding: identity.
|
||||||
|
content-type: application/mercurial-0.1.
|
||||||
|
vary: X-HgArg-1.
|
||||||
|
x-hgarg-1: key=c0ceccb3b2f0f5c977ff32b9337519e5f37942c2&namespace=phases&new=0&old=1.
|
||||||
|
accept: application/mercurial-0.1.
|
||||||
|
authorization: Basic c2NtYWRtaW46c2NtYWRtaW4=.
|
||||||
|
content-length: 0.
|
||||||
|
host: localhost:8080.
|
||||||
|
user-agent: mercurial/proto-1.0 (Mercurial 4.3.1).
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK.
|
||||||
|
Set-Cookie: JSESSIONID=5zrop5v8e661ipk12tvru525;Path=/scm.
|
||||||
|
Expires: Thu, 01 Jan 1970 00:00:00 GMT.
|
||||||
|
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 28-Mar-2018 06:03:35 GMT.
|
||||||
|
Content-Type: application/mercurial-0.1.
|
||||||
|
Content-Length: 2.
|
||||||
|
Server: Jetty(7.6.21.v20160908).
|
||||||
|
|
||||||
|
1
|
||||||
|
```
|
||||||
193
pom.xml
193
pom.xml
@@ -376,6 +376,70 @@
|
|||||||
<version>3.10.0</version>
|
<version>3.10.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- utils -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-beanutils</groupId>
|
||||||
|
<artifactId>commons-beanutils</artifactId>
|
||||||
|
<version>1.9.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-collections</groupId>
|
||||||
|
<artifactId>commons-collections</artifactId>
|
||||||
|
<version>3.2.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- http -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.5.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- logging -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<version>${logback.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- xml -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
<version>${jaxb.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-impl</artifactId>
|
||||||
|
<version>${jaxb.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
|
<version>${jaxb.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.activation</groupId>
|
||||||
|
<artifactId>activation</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -418,10 +482,11 @@
|
|||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>2.22.0</version>
|
<version>2.22.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-enforcer-plugin</artifactId>
|
<artifactId>maven-enforcer-plugin</artifactId>
|
||||||
<version>1.4.1</version>
|
<version>3.0.0-M1</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>enforce-java</id>
|
<id>enforce-java</id>
|
||||||
@@ -443,11 +508,31 @@
|
|||||||
<requireMavenVersion>
|
<requireMavenVersion>
|
||||||
<version>[3.1,)</version>
|
<version>[3.1,)</version>
|
||||||
</requireMavenVersion>
|
</requireMavenVersion>
|
||||||
|
<!--
|
||||||
|
enforce java 1.8 compatible bytecode
|
||||||
|
-->
|
||||||
|
<enforceBytecodeVersion>
|
||||||
|
<maxJdkVersion>1.8</maxJdkVersion>
|
||||||
|
<ignoreClasses>
|
||||||
|
<!--
|
||||||
|
ignore java 9 module info classes
|
||||||
|
because jaxb is compiled with java 7 expect of module-info, which is compiled with java 9
|
||||||
|
-->
|
||||||
|
<ignoreClass>module-info</ignoreClass>
|
||||||
|
</ignoreClasses>
|
||||||
|
</enforceBytecodeVersion>
|
||||||
</rules>
|
</rules>
|
||||||
<fail>true</fail>
|
<fail>true</fail>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>extra-enforcer-rules</artifactId>
|
||||||
|
<version>1.0-beta-7</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
@@ -636,53 +721,53 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-site-plugin</artifactId>
|
<artifactId>maven-site-plugin</artifactId>
|
||||||
<version>3.2</version>
|
<version>3.7</version>
|
||||||
<configuration>
|
|
||||||
<reportPlugins>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
|
||||||
<version>2.4</version>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-jxr-plugin</artifactId>
|
|
||||||
<version>2.3</version>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
|
||||||
<artifactId>findbugs-maven-plugin</artifactId>
|
|
||||||
<version>2.4.0</version>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-surefire-report-plugin</artifactId>
|
|
||||||
<version>2.12</version>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-pmd-plugin</artifactId>
|
|
||||||
<version>2.7.1</version>
|
|
||||||
<configuration>
|
|
||||||
<linkXref>true</linkXref>
|
|
||||||
<sourceEncoding>${project.build.sourceEncoding}</sourceEncoding>
|
|
||||||
<targetJdk>${project.build.javaLevel}</targetJdk>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
</reportPlugins>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
<reporting>
|
||||||
|
<plugins>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||||
|
<version>2.4</version>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jxr-plugin</artifactId>
|
||||||
|
<version>2.3</version>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>findbugs-maven-plugin</artifactId>
|
||||||
|
<version>2.4.0</version>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-report-plugin</artifactId>
|
||||||
|
<version>2.12</version>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-pmd-plugin</artifactId>
|
||||||
|
<version>2.7.1</version>
|
||||||
|
<configuration>
|
||||||
|
<sourceEncoding>${project.build.sourceEncoding}</sourceEncoding>
|
||||||
|
<targetJdk>${project.build.javaLevel}</targetJdk>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</reporting>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
|
||||||
<profile>
|
<profile>
|
||||||
@@ -726,17 +811,9 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<doclet>org.jboss.apiviz.APIviz</doclet>
|
<failOnError>false</failOnError>
|
||||||
<docletArtifact>
|
|
||||||
<groupId>org.jboss.apiviz</groupId>
|
|
||||||
<artifactId>apiviz</artifactId>
|
|
||||||
<version>1.3.2.GA</version>
|
|
||||||
</docletArtifact>
|
|
||||||
<additionalparam>
|
|
||||||
-sourceclasspath ${project.build.outputDirectory}
|
|
||||||
-nopackagediagram
|
|
||||||
</additionalparam>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
@@ -770,8 +847,8 @@
|
|||||||
<junit.version>5.2.0</junit.version>
|
<junit.version>5.2.0</junit.version>
|
||||||
|
|
||||||
<!-- logging libraries -->
|
<!-- logging libraries -->
|
||||||
<slf4j.version>1.7.22</slf4j.version>
|
<slf4j.version>1.7.25</slf4j.version>
|
||||||
<logback.version>1.1.10</logback.version>
|
<logback.version>1.2.3</logback.version>
|
||||||
<servlet.version>3.0.1</servlet.version>
|
<servlet.version>3.0.1</servlet.version>
|
||||||
|
|
||||||
<jaxrs.version>2.0.1</jaxrs.version>
|
<jaxrs.version>2.0.1</jaxrs.version>
|
||||||
@@ -780,6 +857,7 @@
|
|||||||
<enunciate.version>2.11.1</enunciate.version>
|
<enunciate.version>2.11.1</enunciate.version>
|
||||||
<jackson.version>2.8.6</jackson.version>
|
<jackson.version>2.8.6</jackson.version>
|
||||||
<guice.version>4.0</guice.version>
|
<guice.version>4.0</guice.version>
|
||||||
|
<jaxb.version>2.3.0</jaxb.version>
|
||||||
|
|
||||||
<!-- event bus -->
|
<!-- event bus -->
|
||||||
<legman.version>1.4.2</legman.version>
|
<legman.version>1.4.2</legman.version>
|
||||||
@@ -792,9 +870,9 @@
|
|||||||
<ssp.version>1.1.0</ssp.version>
|
<ssp.version>1.1.0</ssp.version>
|
||||||
<shiro.version>1.4.0</shiro.version>
|
<shiro.version>1.4.0</shiro.version>
|
||||||
|
|
||||||
<!-- repostitory libraries -->
|
<!-- repository libraries -->
|
||||||
<jgit.version>v4.5.2.201704071617-r-scm1</jgit.version>
|
<jgit.version>v4.5.3.201708160445-r-scm1</jgit.version>
|
||||||
<svnkit.version>1.8.15-scm1</svnkit.version>
|
<svnkit.version>1.9.0-scm3</svnkit.version>
|
||||||
|
|
||||||
<!-- util libraries -->
|
<!-- util libraries -->
|
||||||
<guava.version>26.0-jre</guava.version>
|
<guava.version>26.0-jre</guava.version>
|
||||||
@@ -806,6 +884,7 @@
|
|||||||
|
|
||||||
<!-- build properties -->
|
<!-- build properties -->
|
||||||
<project.build.javaLevel>1.8</project.build.javaLevel>
|
<project.build.javaLevel>1.8</project.build.javaLevel>
|
||||||
|
<project.test.javaLevel>1.8</project.test.javaLevel>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<netbeans.hint.license>SCM-BSD</netbeans.hint.license>
|
<netbeans.hint.license>SCM-BSD</netbeans.hint.license>
|
||||||
<jdk.classifier />
|
<jdk.classifier />
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.CipherInputStream;
|
||||||
|
import javax.crypto.CipherOutputStream;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link CipherStreamHandler} which uses AES. This version is used since version 1.60 for the
|
||||||
|
* cli client encryption.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
public class AesCipherStreamHandler implements CipherStreamHandler {
|
||||||
|
|
||||||
|
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5PADDING";
|
||||||
|
private static final String SECRET_KEY_ALGORITHM = "AES";
|
||||||
|
private static final int IV_LENGTH = 16;
|
||||||
|
|
||||||
|
private final SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
private final byte[] secretKey;
|
||||||
|
|
||||||
|
AesCipherStreamHandler(String secretKey) {
|
||||||
|
this.secretKey = secretKey.getBytes(Charsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream encrypt(OutputStream outputStream) throws IOException {
|
||||||
|
Cipher cipher = createCipherForEncryption();
|
||||||
|
outputStream.write(cipher.getIV());
|
||||||
|
return new CipherOutputStream(outputStream, cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream decrypt(InputStream inputStream) throws IOException {
|
||||||
|
Cipher cipher = createCipherForDecryption(inputStream);
|
||||||
|
return new CipherInputStream(inputStream, cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher createCipherForDecryption(InputStream inputStream) throws IOException {
|
||||||
|
byte[] iv = createEmptyIvArray();
|
||||||
|
inputStream.read(iv);
|
||||||
|
return createCipher(Cipher.DECRYPT_MODE, iv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] createEmptyIvArray() {
|
||||||
|
return new byte[IV_LENGTH];
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher createCipherForEncryption() {
|
||||||
|
byte[] iv = generateIV();
|
||||||
|
return createCipher(Cipher.ENCRYPT_MODE, iv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] generateIV() {
|
||||||
|
// use 12 byte as described at nist
|
||||||
|
// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||||
|
byte[] iv = createEmptyIvArray();
|
||||||
|
random.nextBytes(iv);
|
||||||
|
return iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher createCipher(int mode, byte[] iv) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||||
|
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
|
||||||
|
cipher.init(mode, new SecretKeySpec(secretKey, SECRET_KEY_ALGORITHM), ivParameterSpec);
|
||||||
|
return cipher;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new ScmConfigException("failed to create cipher", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CipherStreamHandler is able to encrypt and decrypt streams.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
public interface CipherStreamHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the given input stream.
|
||||||
|
*
|
||||||
|
* @param inputStream encrypted input stream
|
||||||
|
*
|
||||||
|
* @return raw input stream
|
||||||
|
*/
|
||||||
|
InputStream decrypt(InputStream inputStream) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts the given output stream.
|
||||||
|
*
|
||||||
|
* @param outputStream raw output stream
|
||||||
|
*
|
||||||
|
* @return encrypting output stream
|
||||||
|
*/
|
||||||
|
OutputStream encrypt(OutputStream outputStream) throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import sonia.scm.security.KeyGenerator;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXB;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util methods for configuration files.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
final class ConfigFiles {
|
||||||
|
|
||||||
|
private static final KeyGenerator keyGenerator = new SecureRandomKeyGenerator();
|
||||||
|
|
||||||
|
// SCM Config Version 2
|
||||||
|
@VisibleForTesting
|
||||||
|
static final byte[] VERSION_IDENTIFIER = "SCV2".getBytes(Charsets.US_ASCII);
|
||||||
|
|
||||||
|
private ConfigFiles() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the file is encrypted with the v2 format.
|
||||||
|
*
|
||||||
|
* @param file configuration file
|
||||||
|
*
|
||||||
|
* @return {@code true} for format v2
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static boolean isFormatV2(File file) throws IOException {
|
||||||
|
try (InputStream input = new FileInputStream(file)) {
|
||||||
|
byte[] bytes = new byte[VERSION_IDENTIFIER.length];
|
||||||
|
input.read(bytes);
|
||||||
|
return Arrays.equals(VERSION_IDENTIFIER, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt and parse v1 configuration file.
|
||||||
|
*
|
||||||
|
* @param secretKeyStore key store
|
||||||
|
* @param file configuration file
|
||||||
|
*
|
||||||
|
* @return client configuration
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static ScmClientConfig parseV1(SecretKeyStore secretKeyStore, File file) throws IOException {
|
||||||
|
String secretKey = secretKey(secretKeyStore);
|
||||||
|
CipherStreamHandler cipherStreamHandler = new WeakCipherStreamHandler(secretKey);
|
||||||
|
return decrypt(cipherStreamHandler, new FileInputStream(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt and parse v12configuration file.
|
||||||
|
*
|
||||||
|
* @param secretKeyStore key store
|
||||||
|
* @param file configuration file
|
||||||
|
*
|
||||||
|
* @return client configuration
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static ScmClientConfig parseV2(SecretKeyStore secretKeyStore, File file) throws IOException {
|
||||||
|
String secretKey = secretKey(secretKeyStore);
|
||||||
|
CipherStreamHandler cipherStreamHandler = new AesCipherStreamHandler(secretKey);
|
||||||
|
try (InputStream input = new FileInputStream(file)) {
|
||||||
|
input.skip(VERSION_IDENTIFIER.length);
|
||||||
|
return decrypt(cipherStreamHandler, input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store encrypt and write the configuration to the given file.
|
||||||
|
* Note the method uses always the latest available format.
|
||||||
|
*
|
||||||
|
* @param secretKeyStore key store
|
||||||
|
* @param config configuration
|
||||||
|
* @param file configuration file
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static void store(SecretKeyStore secretKeyStore, ScmClientConfig config, File file) throws IOException {
|
||||||
|
String secretKey = keyGenerator.createKey();
|
||||||
|
CipherStreamHandler cipherStreamHandler = new AesCipherStreamHandler(secretKey);
|
||||||
|
try (OutputStream output = new FileOutputStream(file)) {
|
||||||
|
output.write(VERSION_IDENTIFIER);
|
||||||
|
encrypt(cipherStreamHandler, output, config);
|
||||||
|
}
|
||||||
|
secretKeyStore.set(secretKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String secretKey(SecretKeyStore secretKeyStore) {
|
||||||
|
String secretKey = secretKeyStore.get();
|
||||||
|
Preconditions.checkState(!Strings.isNullOrEmpty(secretKey), "no stored secret key found");
|
||||||
|
return secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ScmClientConfig decrypt(CipherStreamHandler cipherStreamHandler, InputStream input) throws IOException {
|
||||||
|
try ( InputStream decryptedInputStream = cipherStreamHandler.decrypt(input) ) {
|
||||||
|
return JAXB.unmarshal(decryptedInputStream, ScmClientConfig.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void encrypt(CipherStreamHandler cipherStreamHandler, OutputStream output, ScmClientConfig clientConfig) throws IOException {
|
||||||
|
try ( OutputStream encryptedOutputStream = cipherStreamHandler.encrypt(output) ) {
|
||||||
|
JAXB.marshal(clientConfig, encryptedOutputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EncryptionSecretKeyStoreWrapper is a wrapper around the {@link SecretKeyStore} interface. The wrapper will
|
||||||
|
* encrypt the passed secret keys, before they are written to the underlying {@link SecretKeyStore} implementation. The
|
||||||
|
* wrapper will also honor old unencrypted keys.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
public class EncryptionSecretKeyStoreWrapper implements SecretKeyStore {
|
||||||
|
|
||||||
|
private static final String ALGORITHM = "AES";
|
||||||
|
|
||||||
|
private static final SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
// i know storing the key directly in the class is far away from a best practice, but this is a chicken egg type
|
||||||
|
// of problem. We need a key to encrypt the stored keys, however encrypting the keys with a static defined key
|
||||||
|
// is better as storing them as plain text.
|
||||||
|
private static final byte[] SECRET_KEY = new byte[]{ 0x50, 0x61, 0x41, 0x67, 0x55, 0x43, 0x48, 0x7a, 0x48, 0x59,
|
||||||
|
0x7a, 0x57, 0x6b, 0x34, 0x54, 0x62
|
||||||
|
};
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String ENCRYPTED_PREFIX = "SKV2:";
|
||||||
|
|
||||||
|
private SecretKeyStore wrappedSecretKeyStore;
|
||||||
|
|
||||||
|
EncryptionSecretKeyStoreWrapper(SecretKeyStore wrappedSecretKeyStore) {
|
||||||
|
this.wrappedSecretKeyStore = wrappedSecretKeyStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(String secretKey) {
|
||||||
|
String encrypted = encrypt(secretKey);
|
||||||
|
wrappedSecretKeyStore.set(ENCRYPTED_PREFIX.concat(encrypted));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encrypt(String value) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = createCipher(Cipher.ENCRYPT_MODE);
|
||||||
|
byte[] raw = cipher.doFinal(value.getBytes(Charsets.UTF_8));
|
||||||
|
return encode(raw);
|
||||||
|
} catch (IllegalBlockSizeException | BadPaddingException ex) {
|
||||||
|
throw new ScmConfigException("failed to encrypt key", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encode(byte[] raw) {
|
||||||
|
return BaseEncoding.base64().encode(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get() {
|
||||||
|
String value = wrappedSecretKeyStore.get();
|
||||||
|
if (Strings.nullToEmpty(value).startsWith(ENCRYPTED_PREFIX)) {
|
||||||
|
String encrypted = value.substring(ENCRYPTED_PREFIX.length());
|
||||||
|
return decrypt(encrypted);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String decrypt(String encoded) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = createCipher(Cipher.DECRYPT_MODE);
|
||||||
|
byte[] raw = decode(encoded);
|
||||||
|
return new String(cipher.doFinal(raw), Charsets.UTF_8);
|
||||||
|
} catch (IllegalBlockSizeException | BadPaddingException ex) {
|
||||||
|
throw new ScmConfigException("failed to decrypt key", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] decode(String encoded) {
|
||||||
|
return BaseEncoding.base64().decode(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher createCipher(int mode) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||||
|
SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY, "AES");
|
||||||
|
cipher.init(mode, secretKeySpec, random);
|
||||||
|
return cipher;
|
||||||
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException ex) {
|
||||||
|
throw new ScmConfigException("failed to create key", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
wrappedSecretKeyStore.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SecretKeyStore implementation with uses {@link Preferences}.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
public class PrefsSecretKeyStore implements SecretKeyStore {
|
||||||
|
|
||||||
|
private static final String PREF_SECRET_KEY = "scm.client.key";
|
||||||
|
|
||||||
|
private final Preferences preferences;
|
||||||
|
|
||||||
|
PrefsSecretKeyStore() {
|
||||||
|
// we use ScmClientConfigFileHandler as base for backward compatibility
|
||||||
|
preferences = Preferences.userNodeForPackage(ScmClientConfigFileHandler.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(String secretKey) {
|
||||||
|
preferences.put(PREF_SECRET_KEY, secretKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get() {
|
||||||
|
return preferences.get(PREF_SECRET_KEY, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
preferences.remove(PREF_SECRET_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SecretKeyStore is able to read and write secret keys.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
public interface SecretKeyStore {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the given secret key to the store.
|
||||||
|
*
|
||||||
|
* @param secretKey secret key to write
|
||||||
|
*/
|
||||||
|
void set(String secretKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the secret key from the store. The method returns {@code null} if no secret key was stored.
|
||||||
|
*
|
||||||
|
* @return secret key or {@code null}
|
||||||
|
*/
|
||||||
|
String get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the secret key from store.
|
||||||
|
*/
|
||||||
|
void remove();
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import sonia.scm.security.KeyGenerator;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create keys by using {@link SecureRandom}. The SecureRandomKeyGenerator produces aes compatible keys.
|
||||||
|
* Warning the class is not thread safe.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*/
|
||||||
|
public class SecureRandomKeyGenerator implements KeyGenerator {
|
||||||
|
|
||||||
|
private SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
|
// key length 16 for aes128
|
||||||
|
@VisibleForTesting
|
||||||
|
static final int KEY_LENGTH = 16;
|
||||||
|
|
||||||
|
private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
private static final String LOWER = UPPER.toLowerCase(Locale.ENGLISH);
|
||||||
|
private static final String DIGITS = "0123456789";
|
||||||
|
private static final char[] ALL = (UPPER + LOWER + DIGITS).toCharArray();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createKey() {
|
||||||
|
char[] key = new char[KEY_LENGTH];
|
||||||
|
for (int idx = 0; idx < KEY_LENGTH; ++idx) {
|
||||||
|
key[idx] = ALL[random.nextInt(ALL.length)];
|
||||||
|
}
|
||||||
|
return new String(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import javax.crypto.*;
|
||||||
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
import javax.crypto.spec.PBEParameterSpec;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weak implementation of {@link CipherStreamHandler}. This is the old implementation, which was used in versions prior
|
||||||
|
* 1.60.
|
||||||
|
*
|
||||||
|
* @author Sebastian Sdorra
|
||||||
|
* @since 1.60
|
||||||
|
*
|
||||||
|
* @see <a href="https://bitbucket.org/sdorra/scm-manager/issues/978/iteration-count-for-password-based">Issue 978</a>
|
||||||
|
* @see <a href="https://bitbucket.org/sdorra/scm-manager/issues/979/constant-salts-for-pbe-are-insecure">Issue 979</a>
|
||||||
|
*/
|
||||||
|
public class WeakCipherStreamHandler implements CipherStreamHandler {
|
||||||
|
|
||||||
|
private static final String SALT = "AE16347F";
|
||||||
|
private static final int SPEC_ITERATION = 12;
|
||||||
|
private static final String CIPHER_NAME = "PBEWithMD5AndDES";
|
||||||
|
|
||||||
|
private final char[] secretKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new handler with the given secret key.
|
||||||
|
*
|
||||||
|
* @param secretKey secret key
|
||||||
|
*/
|
||||||
|
WeakCipherStreamHandler(String secretKey) {
|
||||||
|
this.secretKey = secretKey.toCharArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream decrypt(InputStream inputStream) {
|
||||||
|
try {
|
||||||
|
Cipher c = createCipher(Cipher.DECRYPT_MODE);
|
||||||
|
return new CipherInputStream(inputStream, c);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new ScmConfigException("could not encrypt output stream", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream encrypt(OutputStream outputStream) {
|
||||||
|
try {
|
||||||
|
Cipher c = createCipher(Cipher.ENCRYPT_MODE);
|
||||||
|
return new CipherOutputStream(outputStream, c);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new ScmConfigException("could not encrypt output stream", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher createCipher(int mode)
|
||||||
|
throws NoSuchAlgorithmException, NoSuchPaddingException,
|
||||||
|
InvalidKeySpecException, InvalidKeyException,
|
||||||
|
InvalidAlgorithmParameterException
|
||||||
|
{
|
||||||
|
SecretKey sk = createSecretKey();
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
|
||||||
|
PBEParameterSpec spec = new PBEParameterSpec(SALT.getBytes(), SPEC_ITERATION);
|
||||||
|
|
||||||
|
cipher.init(mode, sk, spec);
|
||||||
|
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey createSecretKey()
|
||||||
|
throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||||
|
{
|
||||||
|
PBEKeySpec keySpec = new PBEKeySpec(secretKey);
|
||||||
|
SecretKeyFactory factory = SecretKeyFactory.getInstance(CIPHER_NAME);
|
||||||
|
|
||||||
|
return factory.generateSecret(keySpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.security.KeyGenerator;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class AesCipherStreamHandlerTest {
|
||||||
|
|
||||||
|
private final KeyGenerator keyGenerator = new SecureRandomKeyGenerator();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptAndDecrypt() throws IOException {
|
||||||
|
AesCipherStreamHandler cipherStreamHandler = new AesCipherStreamHandler(keyGenerator.createKey());
|
||||||
|
|
||||||
|
// douglas adams
|
||||||
|
String content = "If you try and take a cat apart to see how it works, the first thing you have on your hands is a nonworking cat.";
|
||||||
|
|
||||||
|
// encrypt
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
OutputStream encryptedOutput = cipherStreamHandler.encrypt(output);
|
||||||
|
encryptedOutput.write(content.getBytes(Charsets.UTF_8));
|
||||||
|
encryptedOutput.close();
|
||||||
|
|
||||||
|
InputStream input = new ByteArrayInputStream(output.toByteArray());
|
||||||
|
input = cipherStreamHandler.decrypt(input);
|
||||||
|
byte[] decrypted = ByteStreams.toByteArray(input);
|
||||||
|
|
||||||
|
assertEquals(content, new String(decrypted, Charsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXB;
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
final class ClientConfigurationTests {
|
||||||
|
|
||||||
|
private ClientConfigurationTests() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testCipherStream(CipherStreamHandler cipherStreamHandler, String content) throws IOException {
|
||||||
|
byte[] encrypted = encrypt(cipherStreamHandler, content);
|
||||||
|
String decrypted = decrypt(cipherStreamHandler, encrypted);
|
||||||
|
assertEquals(content, decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static byte[] encrypt(CipherStreamHandler cipherStreamHandler, String content) throws IOException {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
OutputStream encryptedOutput = cipherStreamHandler.encrypt(output);
|
||||||
|
encryptedOutput.write(content.getBytes(Charsets.UTF_8));
|
||||||
|
encryptedOutput.close();
|
||||||
|
return output.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String decrypt(CipherStreamHandler cipherStreamHandler, byte[] encrypted) throws IOException {
|
||||||
|
InputStream input = new ByteArrayInputStream(encrypted);
|
||||||
|
input = cipherStreamHandler.decrypt(input);
|
||||||
|
byte[] decrypted = ByteStreams.toByteArray(input);
|
||||||
|
input.close();
|
||||||
|
|
||||||
|
return new String(decrypted, Charsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertSampleConfig(ScmClientConfig config) {
|
||||||
|
ServerConfig defaultConfig;
|
||||||
|
defaultConfig = config.getDefaultConfig();
|
||||||
|
|
||||||
|
assertEquals("http://localhost:8080/scm", defaultConfig.getServerUrl());
|
||||||
|
assertEquals("admin", defaultConfig.getUsername());
|
||||||
|
assertEquals("admin123", defaultConfig.getPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
static ScmClientConfig createSampleConfig() {
|
||||||
|
ScmClientConfig config = new ScmClientConfig();
|
||||||
|
ServerConfig defaultConfig = config.getDefaultConfig();
|
||||||
|
defaultConfig.setServerUrl("http://localhost:8080/scm");
|
||||||
|
defaultConfig.setUsername("admin");
|
||||||
|
defaultConfig.setPassword("admin123");
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encrypt(CipherStreamHandler cipherStreamHandler, ScmClientConfig config, File file) throws IOException {
|
||||||
|
try (OutputStream output = cipherStreamHandler.encrypt(new FileOutputStream(file))) {
|
||||||
|
JAXB.marshal(config, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class ConfigFilesTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsFormatV2() throws IOException {
|
||||||
|
byte[] content = "The door was the way to... to... The Door was The Way".getBytes(Charsets.UTF_8);
|
||||||
|
|
||||||
|
File fileV1 = temporaryFolder.newFile();
|
||||||
|
Files.write(content, fileV1);
|
||||||
|
|
||||||
|
assertFalse(ConfigFiles.isFormatV2(fileV1));
|
||||||
|
|
||||||
|
File fileV2 = temporaryFolder.newFile();
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
baos.write(ConfigFiles.VERSION_IDENTIFIER);
|
||||||
|
baos.write(content);
|
||||||
|
Files.write(baos.toByteArray(), fileV2);
|
||||||
|
|
||||||
|
assertTrue(ConfigFiles.isFormatV2(fileV2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseV1() throws IOException {
|
||||||
|
InMemorySecretKeyStore keyStore = createKeyStore();
|
||||||
|
WeakCipherStreamHandler handler = new WeakCipherStreamHandler(keyStore.get());
|
||||||
|
|
||||||
|
ScmClientConfig config = ClientConfigurationTests.createSampleConfig();
|
||||||
|
File file = temporaryFolder.newFile();
|
||||||
|
ClientConfigurationTests.encrypt(handler, config, file);
|
||||||
|
|
||||||
|
config = ConfigFiles.parseV1(keyStore, file);
|
||||||
|
ClientConfigurationTests.assertSampleConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void storeAndParseV2() throws IOException {
|
||||||
|
InMemorySecretKeyStore keyStore = new InMemorySecretKeyStore();
|
||||||
|
ScmClientConfig config = ClientConfigurationTests.createSampleConfig();
|
||||||
|
File file = temporaryFolder.newFile();
|
||||||
|
|
||||||
|
ConfigFiles.store(keyStore, config, file);
|
||||||
|
|
||||||
|
String key = keyStore.get();
|
||||||
|
assertNotNull(key);
|
||||||
|
|
||||||
|
config = ConfigFiles.parseV2(keyStore, file);
|
||||||
|
ClientConfigurationTests.assertSampleConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InMemorySecretKeyStore createKeyStore() {
|
||||||
|
String secretKey = new SecureRandomKeyGenerator().createKey();
|
||||||
|
InMemorySecretKeyStore keyStore = new InMemorySecretKeyStore();
|
||||||
|
keyStore.set(secretKey);
|
||||||
|
return keyStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class EncryptionSecretKeyStoreWrapperTest {
|
||||||
|
|
||||||
|
private SecretKeyStore secretKeyStore = new InMemorySecretKeyStore();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionKeyStoreWrapper() {
|
||||||
|
EncryptionSecretKeyStoreWrapper wrapper = new EncryptionSecretKeyStoreWrapper(secretKeyStore);
|
||||||
|
wrapper.set("mysecretkey");
|
||||||
|
|
||||||
|
assertEquals("mysecretkey", wrapper.get());
|
||||||
|
assertTrue(secretKeyStore.get().startsWith(EncryptionSecretKeyStoreWrapper.ENCRYPTED_PREFIX));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncryptionKeyStoreWrapperWithOldUnencryptedKey() {
|
||||||
|
secretKeyStore.set("mysecretkey");
|
||||||
|
EncryptionSecretKeyStoreWrapper wrapper = new EncryptionSecretKeyStoreWrapper(secretKeyStore);
|
||||||
|
assertEquals("mysecretkey", wrapper.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
public class InMemorySecretKeyStore implements SecretKeyStore {
|
||||||
|
|
||||||
|
private String secretKey;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(String secretKey) {
|
||||||
|
this.secretKey = secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get() {
|
||||||
|
return secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
this.secretKey = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import com.google.common.io.Resources;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import sonia.scm.security.UUIDKeyGenerator;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class ScmClientConfigFileHandlerTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientConfigFileHandler() throws IOException {
|
||||||
|
File configFile = temporaryFolder.newFile();
|
||||||
|
|
||||||
|
ScmClientConfigFileHandler handler = new ScmClientConfigFileHandler(
|
||||||
|
new EncryptionSecretKeyStoreWrapper(new InMemorySecretKeyStore()), configFile
|
||||||
|
);
|
||||||
|
|
||||||
|
ScmClientConfig config = new ScmClientConfig();
|
||||||
|
ServerConfig defaultConfig = config.getDefaultConfig();
|
||||||
|
defaultConfig.setServerUrl("http://localhost:8080/scm");
|
||||||
|
defaultConfig.setUsername("scmadmin");
|
||||||
|
defaultConfig.setPassword("admin123");
|
||||||
|
handler.write(config);
|
||||||
|
|
||||||
|
assertTrue(configFile.exists());
|
||||||
|
|
||||||
|
config = handler.read();
|
||||||
|
defaultConfig = config.getDefaultConfig();
|
||||||
|
assertEquals("http://localhost:8080/scm", defaultConfig.getServerUrl());
|
||||||
|
assertEquals("scmadmin", defaultConfig.getUsername());
|
||||||
|
assertEquals("admin123", defaultConfig.getPassword());
|
||||||
|
|
||||||
|
handler.delete();
|
||||||
|
|
||||||
|
assertFalse(configFile.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientConfigFileHandlerWithOldConfiguration() throws IOException {
|
||||||
|
File configFile = temporaryFolder.newFile();
|
||||||
|
|
||||||
|
// old implementation has used uuids as keys
|
||||||
|
String key = new UUIDKeyGenerator().createKey();
|
||||||
|
|
||||||
|
WeakCipherStreamHandler weakCipherStreamHandler = new WeakCipherStreamHandler(key);
|
||||||
|
ScmClientConfig clientConfig = ClientConfigurationTests.createSampleConfig();
|
||||||
|
ClientConfigurationTests.encrypt(weakCipherStreamHandler, clientConfig, configFile);
|
||||||
|
|
||||||
|
assertFalse(ConfigFiles.isFormatV2(configFile));
|
||||||
|
|
||||||
|
SecretKeyStore secretKeyStore = new EncryptionSecretKeyStoreWrapper(new InMemorySecretKeyStore());
|
||||||
|
secretKeyStore.set(key);
|
||||||
|
|
||||||
|
ScmClientConfigFileHandler handler = new ScmClientConfigFileHandler(
|
||||||
|
secretKeyStore, configFile
|
||||||
|
);
|
||||||
|
|
||||||
|
ScmClientConfig config = handler.read();
|
||||||
|
ClientConfigurationTests.assertSampleConfig(config);
|
||||||
|
|
||||||
|
// ensure key has changed
|
||||||
|
assertNotEquals(key, secretKeyStore.get());
|
||||||
|
|
||||||
|
// ensure config rewritten with v2
|
||||||
|
assertTrue(ConfigFiles.isFormatV2(configFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientConfigFileHandlerWithRealMigration() throws IOException {
|
||||||
|
URL resource = Resources.getResource("sonia/scm/cli/config/scm-cli-config.enc.xml");
|
||||||
|
byte[] bytes = Resources.toByteArray(resource);
|
||||||
|
|
||||||
|
File configFile = temporaryFolder.newFile();
|
||||||
|
Files.write(bytes, configFile);
|
||||||
|
|
||||||
|
String key = "358e018a-0c3c-4339-8266-3874e597305f";
|
||||||
|
SecretKeyStore secretKeyStore = new EncryptionSecretKeyStoreWrapper(new InMemorySecretKeyStore());
|
||||||
|
secretKeyStore.set(key);
|
||||||
|
|
||||||
|
ScmClientConfigFileHandler handler = new ScmClientConfigFileHandler(
|
||||||
|
secretKeyStore, configFile
|
||||||
|
);
|
||||||
|
|
||||||
|
ScmClientConfig config = handler.read();
|
||||||
|
ServerConfig defaultConfig = config.getDefaultConfig();
|
||||||
|
assertEquals("http://hitchhicker.com/scm", defaultConfig.getServerUrl());
|
||||||
|
assertEquals("tricia", defaultConfig.getUsername());
|
||||||
|
assertEquals("trillian123", defaultConfig.getPassword());
|
||||||
|
|
||||||
|
// ensure key has changed
|
||||||
|
assertNotEquals(key, secretKeyStore.get());
|
||||||
|
|
||||||
|
// ensure config rewritten with v2
|
||||||
|
assertTrue(ConfigFiles.isFormatV2(configFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.cli.config;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class SecureRandomKeyGeneratorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateKey() {
|
||||||
|
SecureRandomKeyGenerator keyGenerator = new SecureRandomKeyGenerator();
|
||||||
|
assertNotNull(keyGenerator.createKey());
|
||||||
|
assertEquals(SecureRandomKeyGenerator.KEY_LENGTH, keyGenerator.createKey().length());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -24,9 +24,9 @@
|
|||||||
<version>${servlet.version}</version>
|
<version>${servlet.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- annotations -->
|
<!-- annotations -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-annotations</artifactId>
|
<artifactId>scm-annotations</artifactId>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- logging -->
|
<!-- logging -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<version>${slf4j.version}</version>
|
<version>${slf4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<artifactId>jcl-over-slf4j</artifactId>
|
<artifactId>jcl-over-slf4j</artifactId>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
@@ -80,7 +81,7 @@
|
|||||||
<artifactId>guice-servlet</artifactId>
|
<artifactId>guice-servlet</artifactId>
|
||||||
<version>${guice.version}</version>
|
<version>${guice.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.inject.extensions</groupId>
|
<groupId>com.google.inject.extensions</groupId>
|
||||||
<artifactId>guice-throwingproviders</artifactId>
|
<artifactId>guice-throwingproviders</artifactId>
|
||||||
@@ -136,29 +137,51 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- event bus -->
|
<!-- event bus -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.legman</groupId>
|
<groupId>com.github.legman</groupId>
|
||||||
<artifactId>core</artifactId>
|
<artifactId>core</artifactId>
|
||||||
<version>${legman.version}</version>
|
<version>${legman.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- xml -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-impl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.activation</groupId>
|
||||||
|
<artifactId>activation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- util -->
|
<!-- util -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
<version>${guava.version}</version>
|
<version>${guava.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-lang</groupId>
|
<groupId>commons-lang</groupId>
|
||||||
<artifactId>commons-lang</artifactId>
|
<artifactId>commons-lang</artifactId>
|
||||||
<version>2.6</version>
|
<version>2.6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- annotation processor -->
|
<!-- annotation processor -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-annotation-processor</artifactId>
|
<artifactId>scm-annotation-processor</artifactId>
|
||||||
@@ -186,13 +209,14 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<useStandardDocletOptions>true</useStandardDocletOptions>
|
<useStandardDocletOptions>true</useStandardDocletOptions>
|
||||||
<charset>${project.build.sourceEncoding}</charset>
|
<charset>${project.build.sourceEncoding}</charset>
|
||||||
@@ -216,9 +240,23 @@
|
|||||||
<link>http://www.slf4j.org/api/</link>
|
<link>http://www.slf4j.org/api/</link>
|
||||||
<link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link>
|
<link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link>
|
||||||
</links>
|
</links>
|
||||||
|
<doclet>org.jboss.apiviz.APIviz</doclet>
|
||||||
|
<docletArtifact>
|
||||||
|
<groupId>org.jboss.apiviz</groupId>
|
||||||
|
<artifactId>apiviz</artifactId>
|
||||||
|
<version>1.3.2.GA</version>
|
||||||
|
</docletArtifact>
|
||||||
|
<additionalOptions>
|
||||||
|
<additionalOption>
|
||||||
|
-sourceclasspath ${project.build.outputDirectory}
|
||||||
|
</additionalOption>
|
||||||
|
<additionalOption>
|
||||||
|
-nopackagediagram
|
||||||
|
</additionalOption>
|
||||||
|
</additionalOptions>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,6 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -55,8 +55,10 @@ public class GitConfig extends RepositoryConfig {
|
|||||||
@XmlElement(name = "gc-expression")
|
@XmlElement(name = "gc-expression")
|
||||||
private String gcExpression;
|
private String gcExpression;
|
||||||
|
|
||||||
public String getGcExpression()
|
@XmlElement(name = "disallow-non-fast-forward")
|
||||||
{
|
private boolean nonFastForwardDisallowed;
|
||||||
|
|
||||||
|
public String getGcExpression() {
|
||||||
return gcExpression;
|
return gcExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +66,14 @@ public class GitConfig extends RepositoryConfig {
|
|||||||
this.gcExpression = gcExpression;
|
this.gcExpression = gcExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isNonFastForwardDisallowed() {
|
||||||
|
return nonFastForwardDisallowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNonFastForwardDisallowed(boolean nonFastForwardDisallowed) {
|
||||||
|
this.nonFastForwardDisallowed = nonFastForwardDisallowed;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@XmlTransient // Only for permission checks, don't serialize to XML
|
@XmlTransient // Only for permission checks, don't serialize to XML
|
||||||
public String getId() {
|
public String getId() {
|
||||||
|
|||||||
@@ -68,17 +68,40 @@ public class GitHookTagProvider implements HookTagProvider {
|
|||||||
|
|
||||||
if (Strings.isNullOrEmpty(tag)){
|
if (Strings.isNullOrEmpty(tag)){
|
||||||
logger.debug("received ref name {} is not a tag", refName);
|
logger.debug("received ref name {} is not a tag", refName);
|
||||||
} else if (rc.getType() == ReceiveCommand.Type.CREATE) {
|
} else if (isCreate(rc)) {
|
||||||
createdTagBuilder.add(new Tag(tag, GitUtil.getId(rc.getNewId())));
|
createdTagBuilder.add(createTagFromNewId(rc, tag));
|
||||||
} else if (rc.getType() == ReceiveCommand.Type.DELETE){
|
} else if (isDelete(rc)){
|
||||||
deletedTagBuilder.add(new Tag(tag, GitUtil.getId(rc.getOldId())));
|
deletedTagBuilder.add(createTagFromOldId(rc, tag));
|
||||||
|
} else if (isUpdate(rc)) {
|
||||||
|
createdTagBuilder.add(createTagFromNewId(rc, tag));
|
||||||
|
deletedTagBuilder.add(createTagFromOldId(rc, tag));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createdTags = createdTagBuilder.build();
|
createdTags = createdTagBuilder.build();
|
||||||
deletedTags = deletedTagBuilder.build();
|
deletedTags = deletedTagBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Tag createTagFromNewId(ReceiveCommand rc, String tag) {
|
||||||
|
return new Tag(tag, GitUtil.getId(rc.getNewId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tag createTagFromOldId(ReceiveCommand rc, String tag) {
|
||||||
|
return new Tag(tag, GitUtil.getId(rc.getOldId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUpdate(ReceiveCommand rc) {
|
||||||
|
return rc.getType() == ReceiveCommand.Type.UPDATE || rc.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDelete(ReceiveCommand rc) {
|
||||||
|
return rc.getType() == ReceiveCommand.Type.DELETE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCreate(ReceiveCommand rc) {
|
||||||
|
return rc.getType() == ReceiveCommand.Type.CREATE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Tag> getCreatedTags() {
|
public List<Tag> getCreatedTags() {
|
||||||
return createdTags;
|
return createdTags;
|
||||||
|
|||||||
@@ -35,79 +35,63 @@ package sonia.scm.web;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
|
import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.transport.ReceivePack;
|
import org.eclipse.jgit.transport.ReceivePack;
|
||||||
import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
|
import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
|
||||||
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
|
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
|
||||||
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
|
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
|
||||||
|
|
||||||
import sonia.scm.repository.GitRepositoryHandler;
|
import sonia.scm.repository.GitRepositoryHandler;
|
||||||
import sonia.scm.repository.spi.HookEventFacade;
|
import sonia.scm.repository.spi.HookEventFacade;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* GitReceivePackFactory creates {@link ReceivePack} objects and assigns the required
|
||||||
|
* Hook components.
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class GitReceivePackFactory
|
public class GitReceivePackFactory implements ReceivePackFactory<HttpServletRequest>
|
||||||
implements ReceivePackFactory<HttpServletRequest>
|
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
private final GitRepositoryHandler handler;
|
||||||
* Constructs ...
|
|
||||||
*
|
private ReceivePackFactory wrapped;
|
||||||
*
|
|
||||||
*
|
private final GitReceiveHook hook;
|
||||||
* @param hookEventFacade
|
|
||||||
* @param handler
|
|
||||||
*/
|
|
||||||
@Inject
|
@Inject
|
||||||
public GitReceivePackFactory(HookEventFacade hookEventFacade,
|
public GitReceivePackFactory(GitRepositoryHandler handler, HookEventFacade hookEventFacade) {
|
||||||
GitRepositoryHandler handler)
|
this.handler = handler;
|
||||||
{
|
this.hook = new GitReceiveHook(hookEventFacade, handler);
|
||||||
hook = new GitReceiveHook(hookEventFacade, handler);
|
this.wrapped = new DefaultReceivePackFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @param repository
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws ServiceNotAuthorizedException
|
|
||||||
* @throws ServiceNotEnabledException
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public ReceivePack create(HttpServletRequest request, Repository repository)
|
public ReceivePack create(HttpServletRequest request, Repository repository)
|
||||||
throws ServiceNotEnabledException, ServiceNotAuthorizedException
|
throws ServiceNotEnabledException, ServiceNotAuthorizedException {
|
||||||
{
|
ReceivePack receivePack = wrapped.create(request, repository);
|
||||||
ReceivePack rpack = defaultFactory.create(request, repository);
|
receivePack.setAllowNonFastForwards(isNonFastForwardAllowed());
|
||||||
|
|
||||||
rpack.setPreReceiveHook(hook);
|
receivePack.setPreReceiveHook(hook);
|
||||||
rpack.setPostReceiveHook(hook);
|
receivePack.setPostReceiveHook(hook);
|
||||||
// apply collecting listener, to be able to check which commits are new
|
// apply collecting listener, to be able to check which commits are new
|
||||||
CollectingPackParserListener.set(rpack);
|
CollectingPackParserListener.set(receivePack);
|
||||||
|
|
||||||
return rpack;
|
return receivePack;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
private boolean isNonFastForwardAllowed() {
|
||||||
|
return ! handler.getConfig().isNonFastForwardDisallowed();
|
||||||
|
}
|
||||||
|
|
||||||
/** Field description */
|
@VisibleForTesting
|
||||||
private DefaultReceivePackFactory defaultFactory =
|
void setWrapped(ReceivePackFactory wrapped) {
|
||||||
new DefaultReceivePackFactory();
|
this.wrapped = wrapped;
|
||||||
|
}
|
||||||
/** Field description */
|
|
||||||
private GitReceiveHook hook;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,20 +32,23 @@
|
|||||||
package sonia.scm.repository.api;
|
package sonia.scm.repository.api;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import java.util.List;
|
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||||
import org.junit.Test;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
import static org.hamcrest.Matchers.*;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import org.mockito.stubbing.OngoingStubbing;
|
|
||||||
import sonia.scm.repository.Tag;
|
import sonia.scm.repository.Tag;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.empty;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link GitHookTagProvider}.
|
* Unit tests for {@link GitHookTagProvider}.
|
||||||
*
|
*
|
||||||
@@ -54,6 +57,11 @@ import sonia.scm.repository.Tag;
|
|||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class GitHookTagProviderTest {
|
public class GitHookTagProviderTest {
|
||||||
|
|
||||||
|
private static final String ZERO = ObjectId.zeroId().getName();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private ReceiveCommand command;
|
private ReceiveCommand command;
|
||||||
|
|
||||||
@@ -73,7 +81,7 @@ public class GitHookTagProviderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetCreatedTags() {
|
public void testGetCreatedTags() {
|
||||||
String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
|
String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
|
||||||
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.CREATE, "refs/tags/1.0.0", revision);
|
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.CREATE, "refs/tags/1.0.0", revision, ZERO);
|
||||||
|
|
||||||
assertTag("1.0.0", revision, provider.getCreatedTags());
|
assertTag("1.0.0", revision, provider.getCreatedTags());
|
||||||
assertThat(provider.getDeletedTags(), empty());
|
assertThat(provider.getDeletedTags(), empty());
|
||||||
@@ -85,7 +93,7 @@ public class GitHookTagProviderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetDeletedTags() {
|
public void testGetDeletedTags() {
|
||||||
String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
|
String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
|
||||||
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.DELETE, "refs/tags/1.0.0", revision);
|
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.DELETE, "refs/tags/1.0.0", ZERO, revision);
|
||||||
|
|
||||||
assertThat(provider.getCreatedTags(), empty());
|
assertThat(provider.getCreatedTags(), empty());
|
||||||
assertTag("1.0.0", revision, provider.getDeletedTags());
|
assertTag("1.0.0", revision, provider.getDeletedTags());
|
||||||
@@ -97,12 +105,25 @@ public class GitHookTagProviderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testWithBranch(){
|
public void testWithBranch(){
|
||||||
String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
|
String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
|
||||||
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.CREATE, "refs/heads/1.0.0", revision);
|
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.CREATE, "refs/heads/1.0.0", revision, revision);
|
||||||
|
|
||||||
assertThat(provider.getCreatedTags(), empty());
|
assertThat(provider.getCreatedTags(), empty());
|
||||||
assertThat(provider.getDeletedTags(), empty());
|
assertThat(provider.getDeletedTags(), empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link GitHookTagProvider} with update command.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUpdateTags() {
|
||||||
|
String newId = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
|
||||||
|
String oldId = "e0f2be968b147ff7043684a7715d2fe852553db4";
|
||||||
|
|
||||||
|
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.UPDATE, "refs/tags/1.0.0", newId, oldId);
|
||||||
|
assertTag("1.0.0", newId, provider.getCreatedTags());
|
||||||
|
assertTag("1.0.0", oldId, provider.getDeletedTags());
|
||||||
|
}
|
||||||
|
|
||||||
private void assertTag(String name, String revision, List<Tag> tags){
|
private void assertTag(String name, String revision, List<Tag> tags){
|
||||||
assertNotNull(tags);
|
assertNotNull(tags);
|
||||||
assertFalse(tags.isEmpty());
|
assertFalse(tags.isEmpty());
|
||||||
@@ -112,18 +133,11 @@ public class GitHookTagProviderTest {
|
|||||||
assertEquals(revision, tag.getRevision());
|
assertEquals(revision, tag.getRevision());
|
||||||
}
|
}
|
||||||
|
|
||||||
private GitHookTagProvider createProvider(ReceiveCommand.Type type, String ref, String id){
|
private GitHookTagProvider createProvider(ReceiveCommand.Type type, String ref, String newId, String oldId){
|
||||||
OngoingStubbing<ObjectId> ongoing;
|
when(command.getNewId()).thenReturn(ObjectId.fromString(newId));
|
||||||
if (type == ReceiveCommand.Type.CREATE){
|
when(command.getOldId()).thenReturn(ObjectId.fromString(oldId));
|
||||||
ongoing = when(command.getNewId());
|
|
||||||
} else {
|
|
||||||
ongoing = when(command.getOldId());
|
|
||||||
}
|
|
||||||
ongoing.thenReturn(ObjectId.fromString(id));
|
|
||||||
|
|
||||||
when(command.getType()).thenReturn(type);
|
when(command.getType()).thenReturn(type);
|
||||||
when(command.getRefName()).thenReturn(ref);
|
when(command.getRefName()).thenReturn(ref);
|
||||||
|
|
||||||
return new GitHookTagProvider(commands);
|
return new GitHookTagProvider(commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.web;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.transport.ReceivePack;
|
||||||
|
import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
import sonia.scm.repository.GitConfig;
|
||||||
|
import sonia.scm.repository.GitRepositoryHandler;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link GitReceivePackFactory}.
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class GitReceivePackFactoryTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private GitRepositoryHandler handler;
|
||||||
|
|
||||||
|
private GitConfig config;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ReceivePackFactory wrappedReceivePackFactory;
|
||||||
|
|
||||||
|
private GitReceivePackFactory factory;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpServletRequest request;
|
||||||
|
|
||||||
|
private Repository repository;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpObjectUnderTest() throws Exception {
|
||||||
|
this.repository = createRepositoryForTesting();
|
||||||
|
|
||||||
|
config = new GitConfig();
|
||||||
|
when(handler.getConfig()).thenReturn(config);
|
||||||
|
|
||||||
|
ReceivePack receivePack = new ReceivePack(repository);
|
||||||
|
when(wrappedReceivePackFactory.create(request, repository)).thenReturn(receivePack);
|
||||||
|
|
||||||
|
factory = new GitReceivePackFactory(handler, null);
|
||||||
|
factory.setWrapped(wrappedReceivePackFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Repository createRepositoryForTesting() throws GitAPIException, IOException {
|
||||||
|
File directory = temporaryFolder.newFolder();
|
||||||
|
return Git.init().setDirectory(directory).call().getRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreate() throws Exception {
|
||||||
|
ReceivePack receivePack = factory.create(request, repository);
|
||||||
|
assertThat(receivePack.getPackParserListener(), instanceOf(CollectingPackParserListener.class));
|
||||||
|
assertThat(receivePack.getPreReceiveHook(), instanceOf(GitReceiveHook.class));
|
||||||
|
assertThat(receivePack.getPostReceiveHook(), instanceOf(GitReceiveHook.class));
|
||||||
|
assertTrue(receivePack.isAllowNonFastForwards());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateWithDisabledNonFastForward() throws Exception {
|
||||||
|
config.setNonFastForwardDisallowed(true);
|
||||||
|
ReceivePack receivePack = factory.create(request, repository);
|
||||||
|
assertFalse(receivePack.isAllowNonFastForwards());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aragost.javahg</groupId>
|
<groupId>com.aragost.javahg</groupId>
|
||||||
<artifactId>javahg</artifactId>
|
<artifactId>javahg</artifactId>
|
||||||
<version>0.8-scm1</version>
|
<version>0.13-java7</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
@@ -81,7 +81,6 @@
|
|||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ public class HgConfig extends RepositoryConfig
|
|||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@XmlTransient // Only for permission checks, don't serialize to XML
|
||||||
|
public String getId() {
|
||||||
|
// Don't change this without migrating SCM permission configuration!
|
||||||
|
return PERMISSION;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -124,6 +132,14 @@ public class HgConfig extends RepositoryConfig
|
|||||||
return useOptimizedBytecode;
|
return useOptimizedBytecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDisableHookSSLValidation() {
|
||||||
|
return disableHookSSLValidation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableHttpPostArgs() {
|
||||||
|
return enableHttpPostArgs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -194,6 +210,10 @@ public class HgConfig extends RepositoryConfig
|
|||||||
this.showRevisionInId = showRevisionInId;
|
this.showRevisionInId = showRevisionInId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setEnableHttpPostArgs(boolean enableHttpPostArgs) {
|
||||||
|
this.enableHttpPostArgs = enableHttpPostArgs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -205,6 +225,10 @@ public class HgConfig extends RepositoryConfig
|
|||||||
this.useOptimizedBytecode = useOptimizedBytecode;
|
this.useOptimizedBytecode = useOptimizedBytecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDisableHookSSLValidation(boolean disableHookSSLValidation) {
|
||||||
|
this.disableHookSSLValidation = disableHookSSLValidation;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
@@ -225,10 +249,11 @@ public class HgConfig extends RepositoryConfig
|
|||||||
/** Field description */
|
/** Field description */
|
||||||
private boolean showRevisionInId = false;
|
private boolean showRevisionInId = false;
|
||||||
|
|
||||||
@Override
|
private boolean enableHttpPostArgs = false;
|
||||||
@XmlTransient // Only for permission checks, don't serialize to XML
|
|
||||||
public String getId() {
|
/**
|
||||||
// Don't change this without migrating SCM permission configuration!
|
* disable validation of ssl certificates for mercurial hook
|
||||||
return PERMISSION;
|
* @see <a href="https://goo.gl/zH5eY8">Issue 959</a>
|
||||||
}
|
*/
|
||||||
|
private boolean disableHookSSLValidation = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,15 +56,20 @@ import sonia.scm.web.cgi.CGIExecutor;
|
|||||||
import sonia.scm.web.cgi.CGIExecutorFactory;
|
import sonia.scm.web.cgi.CGIExecutorFactory;
|
||||||
import sonia.scm.web.cgi.EnvList;
|
import sonia.scm.web.cgi.EnvList;
|
||||||
|
|
||||||
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Enumeration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -74,6 +79,8 @@ import java.util.Enumeration;
|
|||||||
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY";
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
public static final String ENV_REPOSITORY_NAME = "REPO_NAME";
|
public static final String ENV_REPOSITORY_NAME = "REPO_NAME";
|
||||||
|
|
||||||
@@ -83,6 +90,8 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
/** Field description */
|
/** Field description */
|
||||||
public static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
|
public static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
|
||||||
|
|
||||||
|
private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
public static final String ENV_SESSION_PREFIX = "SCM_";
|
public static final String ENV_SESSION_PREFIX = "SCM_";
|
||||||
|
|
||||||
@@ -268,11 +277,22 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
directory.getAbsolutePath());
|
directory.getAbsolutePath());
|
||||||
|
|
||||||
// add hook environment
|
// add hook environment
|
||||||
|
Map<String, String> environment = executor.getEnvironment().asMutableMap();
|
||||||
|
if (handler.getConfig().isDisableHookSSLValidation()) {
|
||||||
|
// disable ssl validation
|
||||||
|
// Issue 959: https://goo.gl/zH5eY8
|
||||||
|
environment.put(ENV_PYTHON_HTTPS_VERIFY, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable experimental httppostargs protocol of mercurial
|
||||||
|
// Issue 970: https://goo.gl/poascp
|
||||||
|
environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs()));
|
||||||
|
|
||||||
//J-
|
//J-
|
||||||
HgEnvironment.prepareEnvironment(
|
HgEnvironment.prepareEnvironment(
|
||||||
executor.getEnvironment().asMutableMap(),
|
environment,
|
||||||
handler,
|
handler,
|
||||||
hookManager,
|
hookManager,
|
||||||
request
|
request
|
||||||
);
|
);
|
||||||
//J+
|
//J+
|
||||||
|
|||||||
@@ -33,13 +33,23 @@
|
|||||||
|
|
||||||
package sonia.scm.web;
|
package sonia.scm.web;
|
||||||
|
|
||||||
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||||
import sonia.scm.web.filter.PermissionFilter;
|
import sonia.scm.web.filter.PermissionFilter;
|
||||||
|
|
||||||
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Permission filter for mercurial repositories.
|
* Permission filter for mercurial repositories.
|
||||||
@@ -51,14 +61,48 @@ public class HgPermissionFilter extends PermissionFilter
|
|||||||
|
|
||||||
private static final Set<String> READ_METHODS = ImmutableSet.of("GET", "HEAD", "OPTIONS", "TRACE");
|
private static final Set<String> READ_METHODS = ImmutableSet.of("GET", "HEAD", "OPTIONS", "TRACE");
|
||||||
|
|
||||||
public HgPermissionFilter(ScmConfiguration configuration, ScmProviderHttpServlet delegate)
|
private final HgRepositoryHandler repositoryHandler;
|
||||||
|
|
||||||
|
public HgPermissionFilter(ScmConfiguration configuration, ScmProviderHttpServlet delegate, HgRepositoryHandler repositoryHandler)
|
||||||
{
|
{
|
||||||
super(configuration, delegate);
|
super(configuration, delegate);
|
||||||
|
this.repositoryHandler = repositoryHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void service(HttpServletRequest request, HttpServletResponse response, Repository repository) throws IOException, ServletException {
|
||||||
|
super.service(wrapRequestIfRequired(request), response, repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
HttpServletRequest wrapRequestIfRequired(HttpServletRequest request) {
|
||||||
|
if (isHttpPostArgsEnabled()) {
|
||||||
|
return new HgServletRequest(request);
|
||||||
|
}
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWriteRequest(HttpServletRequest request)
|
public boolean isWriteRequest(HttpServletRequest request)
|
||||||
{
|
{
|
||||||
return !READ_METHODS.contains(request.getMethod());
|
if (isHttpPostArgsEnabled()) {
|
||||||
|
return isHttpPostArgsWriteRequest(request);
|
||||||
|
}
|
||||||
|
return isDefaultWriteRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHttpPostArgsEnabled() {
|
||||||
|
return repositoryHandler.getConfig().isEnableHttpPostArgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHttpPostArgsWriteRequest(HttpServletRequest request) {
|
||||||
|
return WireProtocol.isWriteRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDefaultWriteRequest(HttpServletRequest request) {
|
||||||
|
if (READ_METHODS.contains(request.getMethod())) {
|
||||||
|
return WireProtocol.isWriteRequest(request);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ import javax.inject.Inject;
|
|||||||
public class HgPermissionFilterFactory implements ScmProviderHttpServletDecoratorFactory {
|
public class HgPermissionFilterFactory implements ScmProviderHttpServletDecoratorFactory {
|
||||||
|
|
||||||
private final ScmConfiguration configuration;
|
private final ScmConfiguration configuration;
|
||||||
|
private final HgRepositoryHandler repositoryHandler;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HgPermissionFilterFactory(ScmConfiguration configuration) {
|
public HgPermissionFilterFactory(ScmConfiguration configuration, HgRepositoryHandler repositoryHandler) {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
|
this.repositoryHandler = repositoryHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -25,6 +27,6 @@ public class HgPermissionFilterFactory implements ScmProviderHttpServletDecorato
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScmProviderHttpServlet createDecorator(ScmProviderHttpServlet delegate) {
|
public ScmProviderHttpServlet createDecorator(ScmProviderHttpServlet delegate) {
|
||||||
return new HgPermissionFilter(configuration, delegate);
|
return new HgPermissionFilter(configuration, delegate,repositoryHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package sonia.scm.web;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import javax.servlet.ServletInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HgServletInputStream is a wrapper around the original {@link ServletInputStream} and provides some extra
|
||||||
|
* functionality to support the mercurial client.
|
||||||
|
*/
|
||||||
|
public class HgServletInputStream extends ServletInputStream {
|
||||||
|
|
||||||
|
private final ServletInputStream original;
|
||||||
|
private ByteArrayInputStream captured;
|
||||||
|
|
||||||
|
HgServletInputStream(ServletInputStream original) {
|
||||||
|
this.original = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the given amount of bytes from the stream and captures them, if the {@link #read()} methods is called the
|
||||||
|
* captured bytes are returned before the rest of the stream.
|
||||||
|
*
|
||||||
|
* @param size amount of bytes to read
|
||||||
|
*
|
||||||
|
* @return byte array
|
||||||
|
*
|
||||||
|
* @throws IOException if the method is called twice
|
||||||
|
*/
|
||||||
|
public byte[] readAndCapture(int size) throws IOException {
|
||||||
|
Preconditions.checkState(captured == null, "readAndCapture can only be called once per request");
|
||||||
|
|
||||||
|
// TODO should we enforce a limit? to prevent OOM?
|
||||||
|
byte[] bytes = new byte[size];
|
||||||
|
original.read(bytes);
|
||||||
|
captured = new ByteArrayInputStream(bytes);
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (captured != null && captured.available() > 0) {
|
||||||
|
return captured.read();
|
||||||
|
}
|
||||||
|
return original.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
original.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package sonia.scm.web;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link HttpServletRequestWrapper} which adds some functionality in order to support the mercurial client.
|
||||||
|
*/
|
||||||
|
public final class HgServletRequest extends HttpServletRequestWrapper {
|
||||||
|
|
||||||
|
private HgServletInputStream hgServletInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a request object wrapping the given request.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @throws IllegalArgumentException if the request is null
|
||||||
|
*/
|
||||||
|
public HgServletRequest(HttpServletRequest request) {
|
||||||
|
super(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HgServletInputStream getInputStream() throws IOException {
|
||||||
|
if (hgServletInputStream == null) {
|
||||||
|
hgServletInputStream = new HgServletInputStream(super.getInputStream());
|
||||||
|
}
|
||||||
|
return hgServletInputStream;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2018, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.web;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.collect.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.util.HttpUtil;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WireProtocol provides methods for handling the mercurial wire protocol.
|
||||||
|
*
|
||||||
|
* @see <a href="https://goo.gl/WaVJzw">Mercurial Wire Protocol</a>
|
||||||
|
*/
|
||||||
|
public final class WireProtocol {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(WireProtocol.class);
|
||||||
|
|
||||||
|
private static final Set<String> READ_COMMANDS = ImmutableSet.of(
|
||||||
|
"batch", "between", "branchmap", "branches", "capabilities", "changegroup", "changegroupsubset", "clonebundles",
|
||||||
|
"getbundle", "heads", "hello", "listkeys", "lookup", "known", "stream_out",
|
||||||
|
// could not find lheads in the wireprotocol description but mercurial 4.5.2 uses it for clone
|
||||||
|
"lheads"
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Set<String> WRITE_COMMANDS = ImmutableSet.of(
|
||||||
|
"pushkey", "unbundle"
|
||||||
|
);
|
||||||
|
|
||||||
|
private WireProtocol() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the request is a write request. The method will always return {@code true}, expect for the
|
||||||
|
* following cases:
|
||||||
|
*
|
||||||
|
* - no command was specified with the request (is required for the hgweb ui)
|
||||||
|
* - the command in the query string was found in the list of read request
|
||||||
|
* - if query string contains the batch command, then all commands specified in X-HgArg headers must be
|
||||||
|
* in the list of read requests
|
||||||
|
* - in case of enabled HttpPostArgs protocol and query string container the batch command, the header X-HgArgs-Post
|
||||||
|
* is read and the commands which are specified in the body from 0 to the value of X-HgArgs-Post must be in the list
|
||||||
|
* of read requests
|
||||||
|
*
|
||||||
|
* @param request http request
|
||||||
|
*
|
||||||
|
* @return {@code true} for write requests.
|
||||||
|
*/
|
||||||
|
public static boolean isWriteRequest(HttpServletRequest request) {
|
||||||
|
List<String> commands = commandsOf(request);
|
||||||
|
boolean write = isWriteRequest(commands);
|
||||||
|
LOG.trace("mercurial request {} is write: {}", commands, write);
|
||||||
|
return write;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static boolean isWriteRequest(List<String> commands) {
|
||||||
|
return !READ_COMMANDS.containsAll(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static List<String> commandsOf(HttpServletRequest request) {
|
||||||
|
List<String> listOfCmds = Lists.newArrayList();
|
||||||
|
|
||||||
|
String cmd = getCommandFromQueryString(request);
|
||||||
|
if (cmd != null) {
|
||||||
|
listOfCmds.add(cmd);
|
||||||
|
if (isBatchCommand(cmd)) {
|
||||||
|
parseHgArgHeaders(request, listOfCmds);
|
||||||
|
handleHttpPostArgs(request, listOfCmds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(listOfCmds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleHttpPostArgs(HttpServletRequest request, List<String> listOfCmds) {
|
||||||
|
int hgArgsPostSize = request.getIntHeader("X-HgArgs-Post");
|
||||||
|
if (hgArgsPostSize > 0) {
|
||||||
|
|
||||||
|
if (request instanceof HgServletRequest) {
|
||||||
|
HgServletRequest hgRequest = (HgServletRequest) request;
|
||||||
|
|
||||||
|
parseHttpPostArgs(listOfCmds, hgArgsPostSize, hgRequest);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("could not process the httppostargs protocol without HgServletRequest");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseHttpPostArgs(List<String> listOfCmds, int hgArgsPostSize, HgServletRequest hgRequest) {
|
||||||
|
try {
|
||||||
|
byte[] bytes = hgRequest.getInputStream().readAndCapture(hgArgsPostSize);
|
||||||
|
// we use iso-8859-1 for encoding, because the post args are normally http headers which are using iso-8859-1
|
||||||
|
// see https://tools.ietf.org/html/rfc7230#section-3.2.4
|
||||||
|
String hgArgs = new String(bytes, Charsets.ISO_8859_1);
|
||||||
|
String decoded = decodeValue(hgArgs);
|
||||||
|
parseHgCommandHeader(listOfCmds, decoded);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw Throwables.propagate(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseHgArgHeaders(HttpServletRequest request, List<String> listOfCmds) {
|
||||||
|
Enumeration headerNames = request.getHeaderNames();
|
||||||
|
while (headerNames.hasMoreElements()) {
|
||||||
|
String header = (String) headerNames.nextElement();
|
||||||
|
parseHgArgHeader(request, listOfCmds, header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseHgArgHeader(HttpServletRequest request, List<String> listOfCmds, String header) {
|
||||||
|
if (isHgArgHeader(header)) {
|
||||||
|
String value = getHeaderDecoded(request, header);
|
||||||
|
parseHgArgValue(listOfCmds, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseHgArgValue(List<String> listOfCmds, String value) {
|
||||||
|
if (isHgArgCommandHeader(value)) {
|
||||||
|
parseHgCommandHeader(listOfCmds, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseHgCommandHeader(List<String> listOfCmds, String value) {
|
||||||
|
String[] cmds = value.substring(5).split(";");
|
||||||
|
for (String cmd : cmds ) {
|
||||||
|
String normalizedCmd = normalize(cmd);
|
||||||
|
int index = normalizedCmd.indexOf(' ');
|
||||||
|
if (index > 0) {
|
||||||
|
listOfCmds.add(normalizedCmd.substring(0, index));
|
||||||
|
} else {
|
||||||
|
listOfCmds.add(normalizedCmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalize(String cmd) {
|
||||||
|
return cmd.trim().toLowerCase(Locale.ENGLISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isHgArgCommandHeader(String value) {
|
||||||
|
return value.startsWith("cmds=");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getHeaderDecoded(HttpServletRequest request, String header) {
|
||||||
|
return decodeValue(request.getHeader(header));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String decodeValue(String value) {
|
||||||
|
return HttpUtil.decode(Strings.nullToEmpty(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isHgArgHeader(String header) {
|
||||||
|
return header.toLowerCase(Locale.ENGLISH).startsWith("x-hgarg-");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBatchCommand(String cmd) {
|
||||||
|
return "batch".equalsIgnoreCase(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getCommandFromQueryString(HttpServletRequest request) {
|
||||||
|
// we can't use getParameter, because this would inspect the body for form parameters as well
|
||||||
|
Multimap<String, String> queryParameterMap = createQueryParameterMap(request);
|
||||||
|
|
||||||
|
Collection<String> cmd = queryParameterMap.get("cmd");
|
||||||
|
Preconditions.checkArgument(cmd.size() <= 1, "found more than one cmd query parameter");
|
||||||
|
Iterator<String> iterator = cmd.iterator();
|
||||||
|
|
||||||
|
String command = null;
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
command = iterator.next();
|
||||||
|
}
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Multimap<String,String> createQueryParameterMap(HttpServletRequest request) {
|
||||||
|
Multimap<String,String> parameterMap = HashMultimap.create();
|
||||||
|
|
||||||
|
String queryString = request.getQueryString();
|
||||||
|
if (!Strings.isNullOrEmpty(queryString)) {
|
||||||
|
|
||||||
|
String[] parameters = queryString.split("&");
|
||||||
|
for (String parameter : parameters) {
|
||||||
|
int index = parameter.indexOf('=');
|
||||||
|
if (index > 0) {
|
||||||
|
parameterMap.put(parameter.substring(0, index), parameter.substring(index + 1));
|
||||||
|
} else {
|
||||||
|
parameterMap.put(parameter, "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameterMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,22 @@ from collections import defaultdict
|
|||||||
from mercurial import cmdutil,util
|
from mercurial import cmdutil,util
|
||||||
|
|
||||||
cmdtable = {}
|
cmdtable = {}
|
||||||
command = cmdutil.command(cmdtable)
|
|
||||||
|
try:
|
||||||
|
from mercurial import registrar
|
||||||
|
command = registrar.command(cmdtable)
|
||||||
|
except (AttributeError, ImportError):
|
||||||
|
# Fallback to hg < 4.3 support
|
||||||
|
from mercurial import cmdutil
|
||||||
|
command = cmdutil.command(cmdtable)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from mercurial.utils import dateutil
|
||||||
|
_parsedate = dateutil.parsedate
|
||||||
|
except ImportError:
|
||||||
|
# compat with hg < 4.6
|
||||||
|
from mercurial import util
|
||||||
|
_parsedate = util.parsedate
|
||||||
|
|
||||||
FILE_MARKER = '<files>'
|
FILE_MARKER = '<files>'
|
||||||
|
|
||||||
@@ -166,7 +181,7 @@ def collect_sub_repositories(revCtx):
|
|||||||
subrepos[parts[0].strip()] = subrepo
|
subrepos[parts[0].strip()] = subrepo
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hgsubstate = revCtx.filectx('.hgsubstate').data().split('\n')
|
hgsubstate = revCtx.filectx('.hgsubstate').data().split('\n')
|
||||||
for line in hgsubstate:
|
for line in hgsubstate:
|
||||||
@@ -201,7 +216,7 @@ class File_Printer:
|
|||||||
description = 'n/a'
|
description = 'n/a'
|
||||||
if not self.disableLastCommit:
|
if not self.disableLastCommit:
|
||||||
linkrev = self.repo[file.linkrev()]
|
linkrev = self.repo[file.linkrev()]
|
||||||
date = '%d %d' % util.parsedate(linkrev.date())
|
date = '%d %d' % _parsedate(linkrev.date())
|
||||||
description = linkrev.description()
|
description = linkrev.description()
|
||||||
format = '%s %i %s %s\n'
|
format = '%s %i %s %s\n'
|
||||||
if self.transport:
|
if self.transport:
|
||||||
|
|||||||
@@ -36,7 +36,11 @@ from mercurial.hgweb import hgweb, wsgicgi
|
|||||||
|
|
||||||
demandimport.enable()
|
demandimport.enable()
|
||||||
|
|
||||||
u = uimod.ui()
|
try:
|
||||||
|
u = uimod.ui.load()
|
||||||
|
except AttributeError:
|
||||||
|
# For installations earlier than Mercurial 4.1
|
||||||
|
u = uimod.ui()
|
||||||
|
|
||||||
u.setconfig('web', 'push_ssl', 'false')
|
u.setconfig('web', 'push_ssl', 'false')
|
||||||
u.setconfig('web', 'allow_read', '*')
|
u.setconfig('web', 'allow_read', '*')
|
||||||
@@ -45,7 +49,13 @@ u.setconfig('web', 'allow_push', '*')
|
|||||||
u.setconfig('hooks', 'changegroup.scm', 'python:scmhooks.callback')
|
u.setconfig('hooks', 'changegroup.scm', 'python:scmhooks.callback')
|
||||||
u.setconfig('hooks', 'pretxnchangegroup.scm', 'python:scmhooks.callback')
|
u.setconfig('hooks', 'pretxnchangegroup.scm', 'python:scmhooks.callback')
|
||||||
|
|
||||||
|
# pass SCM_HTTP_POST_ARGS to enable experimental httppostargs protocol of mercurial
|
||||||
|
# SCM_HTTP_POST_ARGS is set by HgCGIServlet
|
||||||
|
# Issue 970: https://goo.gl/poascp
|
||||||
|
u.setconfig('experimental', 'httppostargs', os.environ['SCM_HTTP_POST_ARGS'])
|
||||||
|
|
||||||
|
# open repository
|
||||||
|
# SCM_REPOSITORY_PATH contains the repository path and is set by HgCGIServlet
|
||||||
r = hg.repository(u, os.environ['SCM_REPOSITORY_PATH'])
|
r = hg.repository(u, os.environ['SCM_REPOSITORY_PATH'])
|
||||||
application = hgweb(r)
|
application = hgweb(r)
|
||||||
wsgicgi.launch(application)
|
wsgicgi.launch(application)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def printMessages(ui, msgs):
|
|||||||
for line in msgs:
|
for line in msgs:
|
||||||
if line.startswith("_e") or line.startswith("_n"):
|
if line.startswith("_e") or line.startswith("_n"):
|
||||||
line = line[2:];
|
line = line[2:];
|
||||||
ui.warn(line);
|
ui.warn('%s\n' % line.rstrip())
|
||||||
|
|
||||||
def callHookUrl(ui, repo, hooktype, node):
|
def callHookUrl(ui, repo, hooktype, node):
|
||||||
abort = True
|
abort = True
|
||||||
@@ -79,8 +79,10 @@ def callHookUrl(ui, repo, hooktype, node):
|
|||||||
printMessages(ui, msg.splitlines(True))
|
printMessages(ui, msg.splitlines(True))
|
||||||
else:
|
else:
|
||||||
ui.warn( "ERROR: scm-hook failed with an unknown error\n" )
|
ui.warn( "ERROR: scm-hook failed with an unknown error\n" )
|
||||||
|
ui.traceback()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
ui.warn( "scm-hook failed with an exception\n" )
|
ui.warn( "scm-hook failed with an exception\n" )
|
||||||
|
ui.traceback()
|
||||||
return abort
|
return abort
|
||||||
|
|
||||||
def callback(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
def callback(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
header = "%{pattern}"
|
header = "%{pattern}"
|
||||||
changeset = "{rev}:{node}{author}\n{date|hgdate}\n{branch}\n{parents}{join(extras,',')}\n{tags}{file_adds}{file_mods}{file_dels}\n{desc}\0"
|
changeset = "{rev}:{node}{author}\n{date|hgdate}\n{branch}\n{parents}{extras}\n{tags}{file_adds}{file_mods}{file_dels}\n{desc}\0"
|
||||||
tag = "t {tag}\n"
|
tag = "t {tag}\n"
|
||||||
file_add = "a {file_add}\n"
|
file_add = "a {file_add}\n"
|
||||||
file_mod = "m {file_mod}\n"
|
file_mod = "m {file_mod}\n"
|
||||||
file_del = "d {file_del}\n"
|
file_del = "d {file_del}\n"
|
||||||
|
extra = "{key}={value|stringescape},"
|
||||||
footer = "%{pattern}"
|
footer = "%{pattern}"
|
||||||
@@ -31,21 +31,32 @@
|
|||||||
|
|
||||||
package sonia.scm.web;
|
package sonia.scm.web;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
|
import sonia.scm.repository.HgConfig;
|
||||||
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.RepositoryProvider;
|
import sonia.scm.repository.RepositoryProvider;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static sonia.scm.web.WireProtocolRequestMockFactory.CMDS_HEADS_KNOWN_NODES;
|
||||||
|
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.BOOKMARKS;
|
||||||
|
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.PHASES;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link HgPermissionFilter}.
|
* Unit tests for {@link HgPermissionFilter}.
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
@@ -56,13 +67,37 @@ public class HgPermissionFilterTest {
|
|||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private ScmConfiguration configuration;
|
private ScmConfiguration configuration;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private RepositoryProvider repositoryProvider;
|
private RepositoryProvider repositoryProvider;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HgRepositoryHandler hgRepositoryHandler;
|
||||||
|
|
||||||
|
private WireProtocolRequestMockFactory wireProtocol = new WireProtocolRequestMockFactory("/scm/hg/repo");
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private HgPermissionFilter filter;
|
private HgPermissionFilter filter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
when(hgRepositoryHandler.getConfig()).thenReturn(new HgConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link HgPermissionFilter#wrapRequestIfRequired(HttpServletRequest)}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testWrapRequestIfRequired() {
|
||||||
|
assertSame(request, filter.wrapRequestIfRequired(request));
|
||||||
|
|
||||||
|
HgConfig hgConfig = new HgConfig();
|
||||||
|
hgConfig.setEnableHttpPostArgs(true);
|
||||||
|
when(hgRepositoryHandler.getConfig()).thenReturn(hgConfig);
|
||||||
|
|
||||||
|
assertThat(filter.wrapRequestIfRequired(request), is(instanceOf(HgServletRequest.class)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)}.
|
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)}.
|
||||||
*/
|
*/
|
||||||
@@ -73,7 +108,7 @@ public class HgPermissionFilterTest {
|
|||||||
assertFalse(isWriteRequest("HEAD"));
|
assertFalse(isWriteRequest("HEAD"));
|
||||||
assertFalse(isWriteRequest("TRACE"));
|
assertFalse(isWriteRequest("TRACE"));
|
||||||
assertFalse(isWriteRequest("OPTIONS"));
|
assertFalse(isWriteRequest("OPTIONS"));
|
||||||
|
|
||||||
// write methods
|
// write methods
|
||||||
assertTrue(isWriteRequest("POST"));
|
assertTrue(isWriteRequest("POST"));
|
||||||
assertTrue(isWriteRequest("PUT"));
|
assertTrue(isWriteRequest("PUT"));
|
||||||
@@ -81,8 +116,121 @@ public class HgPermissionFilterTest {
|
|||||||
assertTrue(isWriteRequest("KA"));
|
assertTrue(isWriteRequest("KA"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with enabled httppostargs option.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testIsWriteRequestWithEnabledHttpPostArgs() {
|
||||||
|
HgConfig config = new HgConfig();
|
||||||
|
config.setEnableHttpPostArgs(true);
|
||||||
|
when(hgRepositoryHandler.getConfig()).thenReturn(config);
|
||||||
|
|
||||||
|
assertFalse(isWriteRequest("POST"));
|
||||||
|
assertFalse(isWriteRequest("POST", "heads"));
|
||||||
|
assertTrue(isWriteRequest("POST", "unbundle"));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isWriteRequest(String method) {
|
private boolean isWriteRequest(String method) {
|
||||||
|
return isWriteRequest(method, "capabilities");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isWriteRequest(String method, String command) {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
when(request.getQueryString()).thenReturn("cmd=" + command);
|
||||||
when(request.getMethod()).thenReturn(method);
|
when(request.getMethod()).thenReturn(method);
|
||||||
return filter.isWriteRequest(request);
|
return filter.isWriteRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
|
||||||
|
* fresh clone of a repository.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testIsWriteRequestWithClone() {
|
||||||
|
assertIsReadRequest(wireProtocol.capabilities());
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||||
|
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
|
||||||
|
* push of a single changeset.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testIsWriteRequestWithSingleChangesetPush() {
|
||||||
|
assertIsReadRequest(wireProtocol.capabilities());
|
||||||
|
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("c0ceccb3b2f0f5c977ff32b9337519e5f37942c2")));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||||
|
assertIsWriteRequest(wireProtocol.unbundle(261L, "686173686564+6768033e216468247bd031a0a2d9876d79818f8f"));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||||
|
assertIsWriteRequest(wireProtocol.pushkey("c0ceccb3b2f0f5c977ff32b9337519e5f37942c2&namespace=phases&new=0&old=1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
|
||||||
|
* push to a single changeset.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testIsWriteRequestWithMultipleChangesetsPush() {
|
||||||
|
assertIsReadRequest(wireProtocol.capabilities());
|
||||||
|
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("ef5993bb4abb32a0565c347844c6d939fc4f4b98")));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||||
|
assertIsReadRequest(wireProtocol.branchmap());
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||||
|
assertIsWriteRequest(wireProtocol.unbundle(746L, "686173686564+95373ca7cd5371cb6c49bb755ee451d9ec585845"));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||||
|
assertIsWriteRequest(wireProtocol.pushkey("ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
|
||||||
|
* push of multiple branches to a new repository.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testIsWriteRequestWithMutlipleBranchesToNewRepositoryPush() {
|
||||||
|
assertIsReadRequest(wireProtocol.capabilities());
|
||||||
|
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("ef5993bb4abb32a0565c347844c6d939fc4f4b98")));
|
||||||
|
assertIsReadRequest(wireProtocol.known("c0ceccb3b2f0f5c977ff32b9337519e5f37942c2+187ddf37e237c370514487a0bb1a226f11a780b3+b5914611f84eae14543684b2721eec88b0edac12+8b63a323606f10c86b30465570c2574eb7a3a989"));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||||
|
assertIsWriteRequest(wireProtocol.unbundle(913L, "686173686564+6768033e216468247bd031a0a2d9876d79818f8f"));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||||
|
assertIsWriteRequest(wireProtocol.pushkey("ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
|
||||||
|
* push of a bookmark.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testIsWriteRequestWithBookmarkPush() {
|
||||||
|
assertIsReadRequest(wireProtocol.capabilities());
|
||||||
|
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("ef5993bb4abb32a0565c347844c6d939fc4f4b98")));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||||
|
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||||
|
assertIsWriteRequest(wireProtocol.pushkey("markone&namespace=bookmarks&new=ef5993bb4abb32a0565c347844c6d939fc4f4b98&old="));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a write request hidden in a batch GET
|
||||||
|
* request.
|
||||||
|
*
|
||||||
|
* @see <a href="https://goo.gl/poascp">Issue #970</a>
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testIsWriteRequestWithBookmarkPushInABatch() {
|
||||||
|
assertIsWriteRequest(wireProtocol.batch("pushkey key=markthree,namespace=bookmarks,new=187ddf37e237c370514487a0bb1a226f11a780b3,old="));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIsReadRequest(HttpServletRequest request) {
|
||||||
|
assertFalse(filter.isWriteRequest(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIsWriteRequest(HttpServletRequest request) {
|
||||||
|
assertTrue(filter.isWriteRequest(request));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package sonia.scm.web;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.servlet.ServletInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class HgServletInputStreamTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadAndCapture() throws IOException {
|
||||||
|
SampleServletInputStream original = new SampleServletInputStream("trillian.mcmillian@hitchhiker.com");
|
||||||
|
HgServletInputStream hgServletInputStream = new HgServletInputStream(original);
|
||||||
|
|
||||||
|
byte[] prefix = hgServletInputStream.readAndCapture(8);
|
||||||
|
assertEquals("trillian", new String(prefix, Charsets.US_ASCII));
|
||||||
|
|
||||||
|
byte[] wholeBytes = ByteStreams.toByteArray(hgServletInputStream);
|
||||||
|
assertEquals("trillian.mcmillian@hitchhiker.com", new String(wholeBytes, Charsets.US_ASCII));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void testReadAndCaptureCalledTwice() throws IOException {
|
||||||
|
SampleServletInputStream original = new SampleServletInputStream("trillian.mcmillian@hitchhiker.com");
|
||||||
|
HgServletInputStream hgServletInputStream = new HgServletInputStream(original);
|
||||||
|
|
||||||
|
hgServletInputStream.readAndCapture(1);
|
||||||
|
hgServletInputStream.readAndCapture(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SampleServletInputStream extends ServletInputStream {
|
||||||
|
|
||||||
|
private ByteArrayInputStream input;
|
||||||
|
|
||||||
|
private SampleServletInputStream(String data) {
|
||||||
|
input = new ByteArrayInputStream(data.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() {
|
||||||
|
return input.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package sonia.scm.web;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
public class WireProtocolRequestMockFactory {
|
||||||
|
|
||||||
|
public enum Namespace {
|
||||||
|
PHASES, BOOKMARKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String CMDS_HEADS_KNOWN_NODES = "heads+%3Bknown+nodes%3D";
|
||||||
|
|
||||||
|
private String repositoryPath;
|
||||||
|
|
||||||
|
public WireProtocolRequestMockFactory(String repositoryPath) {
|
||||||
|
this.repositoryPath = repositoryPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpServletRequest capabilities() {
|
||||||
|
return base("GET", "cmd=capabilities");
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpServletRequest listkeys(Namespace namespace) {
|
||||||
|
HttpServletRequest request = base("GET", "cmd=capabilities");
|
||||||
|
header(request, "vary", "X-HgArg-1");
|
||||||
|
header(request, "x-hgarg-1", namespaceValue(namespace));
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpServletRequest branchmap() {
|
||||||
|
return base("GET", "cmd=branchmap");
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpServletRequest batch(String... args) {
|
||||||
|
HttpServletRequest request = base("GET", "cmd=batch");
|
||||||
|
args(request, "cmds", args);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpServletRequest unbundle(long contentLength, String... heads) {
|
||||||
|
HttpServletRequest request = base("POST", "cmd=unbundle");
|
||||||
|
header(request, "Content-Length", String.valueOf(contentLength));
|
||||||
|
args(request, "heads", heads);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpServletRequest pushkey(String... keys) {
|
||||||
|
HttpServletRequest request = base("POST", "cmd=pushkey");
|
||||||
|
args(request, "key", keys);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpServletRequest known(String... nodes) {
|
||||||
|
HttpServletRequest request = base("GET", "cmd=known");
|
||||||
|
args(request, "nodes", nodes);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void args(HttpServletRequest request, String prefix, String[] values) {
|
||||||
|
List<String> headers = Lists.newArrayList();
|
||||||
|
|
||||||
|
StringBuilder vary = new StringBuilder();
|
||||||
|
for ( int i=0; i<values.length; i++ ) {
|
||||||
|
String header = "X-HgArg-" + (i+1);
|
||||||
|
|
||||||
|
if (i>0) {
|
||||||
|
vary.append(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
vary.append(header);
|
||||||
|
headers.add(header);
|
||||||
|
|
||||||
|
header(request, header, prefix + "=" + values[i]);
|
||||||
|
}
|
||||||
|
header(request, "Vary", vary.toString());
|
||||||
|
|
||||||
|
when(request.getHeaderNames()).thenReturn(Collections.enumeration(headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpServletRequest base(String method, String queryStringValue) {
|
||||||
|
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||||
|
|
||||||
|
when(request.getRequestURI()).thenReturn(repositoryPath);
|
||||||
|
when(request.getMethod()).thenReturn(method);
|
||||||
|
|
||||||
|
queryString(request, queryStringValue);
|
||||||
|
|
||||||
|
header(request, "Accept", "application/mercurial-0.1");
|
||||||
|
header(request, "Accept-Encoding", "identity");
|
||||||
|
header(request, "User-Agent", "mercurial/proto-1.0 (Mercurial 4.3.1)");
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void queryString(HttpServletRequest request, String queryString) {
|
||||||
|
when(request.getQueryString()).thenReturn(queryString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void header(HttpServletRequest request, String header, String value) {
|
||||||
|
when(request.getHeader(header)).thenReturn(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String namespaceValue(Namespace namespace) {
|
||||||
|
return "namespace=" + namespace.toString().toLowerCase(Locale.ENGLISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2018, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.web;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import javax.servlet.ServletInputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.contains;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link WireProtocol}.
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class WireProtocolTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpServletRequest request;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsWriteRequestOnPost() {
|
||||||
|
assertIsWriteRequest("capabilities", "unbundle");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsWriteRequest() {
|
||||||
|
assertIsWriteRequest("unbundle");
|
||||||
|
assertIsWriteRequest("capabilities", "unbundle");
|
||||||
|
assertIsWriteRequest("capabilities", "postkeys");
|
||||||
|
assertIsReadRequest();
|
||||||
|
assertIsReadRequest("capabilities");
|
||||||
|
assertIsReadRequest("capabilities", "branches", "branchmap");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIsWriteRequest(String... commands) {
|
||||||
|
List<String> cmdList = Lists.newArrayList(commands);
|
||||||
|
assertTrue(WireProtocol.isWriteRequest(cmdList));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIsReadRequest(String... commands) {
|
||||||
|
List<String> cmdList = Lists.newArrayList(commands);
|
||||||
|
assertFalse(WireProtocol.isWriteRequest(cmdList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetCommandsOf() {
|
||||||
|
expectQueryCommand("capabilities", "cmd=capabilities");
|
||||||
|
expectQueryCommand("unbundle", "cmd=unbundle");
|
||||||
|
expectQueryCommand("unbundle", "prefix=stuff&cmd=unbundle");
|
||||||
|
expectQueryCommand("unbundle", "cmd=unbundle&suffix=stuff");
|
||||||
|
expectQueryCommand("unbundle", "prefix=stuff&cmd=unbundle&suffix=stuff");
|
||||||
|
expectQueryCommand("unbundle", "bool=&cmd=unbundle");
|
||||||
|
expectQueryCommand("unbundle", "bool&cmd=unbundle");
|
||||||
|
expectQueryCommand("unbundle", "prefix=stu==ff&cmd=unbundle");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetCommandsOfWithHgArgsPost() throws IOException {
|
||||||
|
when(request.getMethod()).thenReturn("POST");
|
||||||
|
when(request.getQueryString()).thenReturn("cmd=batch");
|
||||||
|
when(request.getIntHeader("X-HgArgs-Post")).thenReturn(29);
|
||||||
|
when(request.getHeaderNames()).thenReturn(Collections.enumeration(Lists.newArrayList("X-HgArgs-Post")));
|
||||||
|
when(request.getInputStream()).thenReturn(new BufferedServletInputStream("cmds=lheads+%3Bknown+nodes%3D"));
|
||||||
|
|
||||||
|
List<String> commands = WireProtocol.commandsOf(new HgServletRequest(request));
|
||||||
|
assertThat(commands, contains("batch", "lheads", "known"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetCommandsOfWithBatch() {
|
||||||
|
prepareBatch("cmds=heads ;known nodes,ef5993bb4abb32a0565c347844c6d939fc4f4b98");
|
||||||
|
List<String> commands = WireProtocol.commandsOf(request);
|
||||||
|
assertThat(commands, contains("batch", "heads", "known"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetCommandsOfWithBatchEncoded() {
|
||||||
|
prepareBatch("cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98");
|
||||||
|
List<String> commands = WireProtocol.commandsOf(request);
|
||||||
|
assertThat(commands, contains("batch", "heads", "known"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetCommandsOfWithBatchAndMutlipleLines() {
|
||||||
|
prepareBatch(
|
||||||
|
"cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98",
|
||||||
|
"cmds=unbundle; postkeys",
|
||||||
|
"cmds= branchmap p1=r2,p2=r4; listkeys"
|
||||||
|
);
|
||||||
|
List<String> commands = WireProtocol.commandsOf(request);
|
||||||
|
assertThat(commands, contains("batch", "heads", "known", "unbundle", "postkeys", "branchmap", "listkeys"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareBatch(String... args) {
|
||||||
|
when(request.getQueryString()).thenReturn("cmd=batch");
|
||||||
|
List<String> headers = Lists.newArrayList();
|
||||||
|
for (int i=0; i<args.length; i++) {
|
||||||
|
String header = "X-HgArg-" + (i+1);
|
||||||
|
headers.add(header);
|
||||||
|
when(request.getHeader(header)).thenReturn(args[i]);
|
||||||
|
}
|
||||||
|
when(request.getHeaderNames()).thenReturn(Collections.enumeration(headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testGetCommandsOfWithMultipleCommandsInQueryString() {
|
||||||
|
when(request.getQueryString()).thenReturn("cmd=abc&cmd=def");
|
||||||
|
WireProtocol.commandsOf(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetCommandsOfWithoutCmdInQueryString() {
|
||||||
|
when(request.getQueryString()).thenReturn("abc=def&123=456");
|
||||||
|
assertTrue(WireProtocol.commandsOf(request).isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetCommandsOfWithEmptyQueryString() {
|
||||||
|
when(request.getQueryString()).thenReturn("");
|
||||||
|
assertTrue(WireProtocol.commandsOf(request).isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetCommandsOfWithNullQueryString() {
|
||||||
|
assertTrue(WireProtocol.commandsOf(request).isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectQueryCommand(String expected, String queryString) {
|
||||||
|
when(request.getQueryString()).thenReturn(queryString);
|
||||||
|
List<String> commands = WireProtocol.commandsOf(request);
|
||||||
|
assertEquals(1, commands.size());
|
||||||
|
assertTrue(commands.contains(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BufferedServletInputStream extends ServletInputStream {
|
||||||
|
|
||||||
|
private ByteArrayInputStream input;
|
||||||
|
|
||||||
|
BufferedServletInputStream(String content) {
|
||||||
|
this.input = new ByteArrayInputStream(content.getBytes(Charsets.US_ASCII));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() {
|
||||||
|
return input.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -306,8 +306,8 @@
|
|||||||
</profiles>
|
</profiles>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<commons.daemon.version>1.0.15</commons.daemon.version>
|
<commons.daemon.version>1.1.0</commons.daemon.version>
|
||||||
<commons.daemon.native.version>1.0.15.1</commons.daemon.native.version>
|
<commons.daemon.native.version>1.1.0</commons.daemon.native.version>
|
||||||
<exploded.directory>${project.build.directory}/appassembler/commons-daemon/scm-server</exploded.directory>
|
<exploded.directory>${project.build.directory}/appassembler/commons-daemon/scm-server</exploded.directory>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<artifactId>scm-core</artifactId>
|
<artifactId>scm-core</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>2.0.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.sdorra</groupId>
|
<groupId>com.github.sdorra</groupId>
|
||||||
<artifactId>shiro-unit</artifactId>
|
<artifactId>shiro-unit</artifactId>
|
||||||
|
|||||||
@@ -18,14 +18,14 @@
|
|||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
<!-- annotation processor -->
|
<!-- annotation processor -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm</groupId>
|
<groupId>sonia.scm</groupId>
|
||||||
<artifactId>scm-annotation-processor</artifactId>
|
<artifactId>scm-annotation-processor</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>2.0.0-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.servlet</groupId>
|
<groupId>javax.servlet</groupId>
|
||||||
<artifactId>javax.servlet-api</artifactId>
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<artifactId>shiro-guice</artifactId>
|
<artifactId>shiro-guice</artifactId>
|
||||||
<version>${shiro.version}</version>
|
<version>${shiro.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt-impl</artifactId>
|
<artifactId>jjwt-impl</artifactId>
|
||||||
@@ -146,11 +146,13 @@
|
|||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>resteasy-servlet-initializer</artifactId>
|
<artifactId>resteasy-servlet-initializer</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>resteasy-validator-provider-11</artifactId>
|
<artifactId>resteasy-validator-provider-11</artifactId>
|
||||||
<version>${resteasy.version}</version>
|
<version>${resteasy.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- injection -->
|
<!-- injection -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -160,19 +162,18 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- event bus -->
|
<!-- event bus -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.legman.support</groupId>
|
<groupId>com.github.legman.support</groupId>
|
||||||
<artifactId>shiro</artifactId>
|
<artifactId>shiro</artifactId>
|
||||||
<version>${legman.version}</version>
|
<version>${legman.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- logging -->
|
<!-- logging -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>logback-classic</artifactId>
|
||||||
<version>${logback.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -186,26 +187,34 @@
|
|||||||
<artifactId>log4j-over-slf4j</artifactId>
|
<artifactId>log4j-over-slf4j</artifactId>
|
||||||
<version>${slf4j.version}</version>
|
<version>${slf4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
fix java.lang.NoClassDefFoundError org/w3c/dom/ElementTraversal
|
||||||
|
-->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>xml-apis</groupId>
|
||||||
|
<artifactId>xml-apis</artifactId>
|
||||||
|
<version>1.4.01</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- only for BeanComparator, replace with own implementation -->
|
<!-- only for BeanComparator, replace with own implementation -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-beanutils</groupId>
|
<groupId>commons-beanutils</groupId>
|
||||||
<artifactId>commons-beanutils</artifactId>
|
<artifactId>commons-beanutils</artifactId>
|
||||||
<version>1.9.2</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-collections</groupId>
|
<groupId>commons-collections</groupId>
|
||||||
<artifactId>commons-collections</artifactId>
|
<artifactId>commons-collections</artifactId>
|
||||||
<version>3.2.1</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
fix installation of httpasswd-plugin
|
fix installation of httpasswd-plugin
|
||||||
https://groups.google.com/d/topic/scmmanager/eN7UtG8TwW8/discussion
|
https://groups.google.com/d/topic/scmmanager/eN7UtG8TwW8/discussion
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-codec</groupId>
|
<groupId>commons-codec</groupId>
|
||||||
<artifactId>commons-codec</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
@@ -229,17 +238,9 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- fix version conflict -->
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpclient</artifactId>
|
|
||||||
<version>4.2.6</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- template engine -->
|
<!-- template engine -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.spullara.mustache.java</groupId>
|
<groupId>com.github.spullara.mustache.java</groupId>
|
||||||
<artifactId>compiler</artifactId>
|
<artifactId>compiler</artifactId>
|
||||||
@@ -277,7 +278,7 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.seleniumhq.selenium</groupId>
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
<artifactId>selenium-java</artifactId>
|
<artifactId>selenium-java</artifactId>
|
||||||
@@ -298,7 +299,7 @@
|
|||||||
<version>2.21</version>
|
<version>2.21</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.sun.jersey</groupId>
|
<groupId>com.sun.jersey</groupId>
|
||||||
<artifactId>jersey-client</artifactId>
|
<artifactId>jersey-client</artifactId>
|
||||||
@@ -320,7 +321,7 @@
|
|||||||
<artifactId>shiro-unit</artifactId>
|
<artifactId>shiro-unit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-git-plugin</artifactId>
|
<artifactId>scm-git-plugin</artifactId>
|
||||||
@@ -328,14 +329,14 @@
|
|||||||
<classifier>tests</classifier>
|
<classifier>tests</classifier>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-git-plugin</artifactId>
|
<artifactId>scm-git-plugin</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>2.0.0-SNAPSHOT</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-hg-plugin</artifactId>
|
<artifactId>scm-hg-plugin</artifactId>
|
||||||
@@ -343,14 +344,14 @@
|
|||||||
<classifier>tests</classifier>
|
<classifier>tests</classifier>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-hg-plugin</artifactId>
|
<artifactId>scm-hg-plugin</artifactId>
|
||||||
<version>2.0.0-SNAPSHOT</version>
|
<version>2.0.0-SNAPSHOT</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-svn-plugin</artifactId>
|
<artifactId>scm-svn-plugin</artifactId>
|
||||||
@@ -358,7 +359,7 @@
|
|||||||
<classifier>tests</classifier>
|
<classifier>tests</classifier>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>sonia.scm.plugins</groupId>
|
<groupId>sonia.scm.plugins</groupId>
|
||||||
<artifactId>scm-svn-plugin</artifactId>
|
<artifactId>scm-svn-plugin</artifactId>
|
||||||
@@ -489,7 +490,7 @@
|
|||||||
<filteringDeploymentDescriptors>true</filteringDeploymentDescriptors>
|
<filteringDeploymentDescriptors>true</filteringDeploymentDescriptors>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>sonia.maven</groupId>
|
<groupId>sonia.maven</groupId>
|
||||||
<artifactId>change-env</artifactId>
|
<artifactId>change-env</artifactId>
|
||||||
@@ -539,8 +540,8 @@
|
|||||||
<jettyXml>${project.basedir}/src/main/conf/jetty.xml</jettyXml>
|
<jettyXml>${project.basedir}/src/main/conf/jetty.xml</jettyXml>
|
||||||
<scanIntervalSeconds>0</scanIntervalSeconds>
|
<scanIntervalSeconds>0</scanIntervalSeconds>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|
||||||
<finalName>scm-webapp</finalName>
|
<finalName>scm-webapp</finalName>
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ package sonia.scm.api.rest.resources;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.net.UrlEscapers;
|
||||||
import org.apache.commons.beanutils.BeanComparator;
|
import org.apache.commons.beanutils.BeanComparator;
|
||||||
import org.apache.shiro.authz.AuthorizationException;
|
import org.apache.shiro.authz.AuthorizationException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -45,7 +47,6 @@ import sonia.scm.ModelObject;
|
|||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
import sonia.scm.api.rest.RestExceptionResult;
|
import sonia.scm.api.rest.RestExceptionResult;
|
||||||
import sonia.scm.util.AssertUtil;
|
import sonia.scm.util.AssertUtil;
|
||||||
import sonia.scm.util.HttpUtil;
|
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
import javax.ws.rs.core.CacheControl;
|
import javax.ws.rs.core.CacheControl;
|
||||||
@@ -63,6 +64,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -139,11 +141,7 @@ public abstract class AbstractManagerResource<T extends ModelObject> {
|
|||||||
manager.create(item);
|
manager.create(item);
|
||||||
|
|
||||||
String id = getId(item);
|
String id = getId(item);
|
||||||
|
response = Response.created(location(uriInfo, id)).build();
|
||||||
id = HttpUtil.encode(id);
|
|
||||||
response = Response.created(
|
|
||||||
uriInfo.getAbsolutePath().resolve(
|
|
||||||
getPathPart().concat("/").concat(id))).build();
|
|
||||||
}
|
}
|
||||||
catch (AuthorizationException ex)
|
catch (AuthorizationException ex)
|
||||||
{
|
{
|
||||||
@@ -159,6 +157,12 @@ public abstract class AbstractManagerResource<T extends ModelObject> {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
URI location(UriInfo uriInfo, String id) {
|
||||||
|
String escaped = UrlEscapers.urlPathSegmentEscaper().escape(id);
|
||||||
|
return uriInfo.getAbsolutePath().resolve(getPathPart().concat("/").concat(escaped));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -247,7 +251,7 @@ public abstract class AbstractManagerResource<T extends ModelObject> {
|
|||||||
*/
|
*/
|
||||||
public Response get(Request request, String id)
|
public Response get(Request request, String id)
|
||||||
{
|
{
|
||||||
Response response = null;
|
Response response;
|
||||||
T item = manager.get(id);
|
T item = manager.get(id);
|
||||||
|
|
||||||
if (item != null)
|
if (item != null)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ package sonia.scm.web.cgi;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
|
|
||||||
@@ -139,12 +140,6 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
|||||||
apendOsEnvironment(env);
|
apendOsEnvironment(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
// workaround for mercurial 2.1
|
|
||||||
if (isContentLengthWorkaround())
|
|
||||||
{
|
|
||||||
env.set(ENV_CONTENT_LENGTH, Integer.toString(request.getContentLength()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workDirectory == null)
|
if (workDirectory == null)
|
||||||
{
|
{
|
||||||
workDirectory = command.getParentFile();
|
workDirectory = command.getParentFile();
|
||||||
@@ -304,26 +299,10 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
|||||||
String uri = HttpUtil.removeMatrixParameter(request.getRequestURI());
|
String uri = HttpUtil.removeMatrixParameter(request.getRequestURI());
|
||||||
String scriptName = uri.substring(0, uri.length() - pathInfo.length());
|
String scriptName = uri.substring(0, uri.length() - pathInfo.length());
|
||||||
String scriptPath = context.getRealPath(scriptName);
|
String scriptPath = context.getRealPath(scriptName);
|
||||||
int len = request.getContentLength();
|
|
||||||
EnvList env = new EnvList();
|
EnvList env = new EnvList();
|
||||||
|
|
||||||
env.set(ENV_AUTH_TYPE, request.getAuthType());
|
env.set(ENV_AUTH_TYPE, request.getAuthType());
|
||||||
|
env.set(ENV_CONTENT_LENGTH, createCGIContentLength(request, contentLengthWorkaround));
|
||||||
/**
|
|
||||||
* Note CGI spec says CONTENT_LENGTH must be NULL ("") or undefined
|
|
||||||
* if there is no content, so we cannot put 0 or -1 in as per the
|
|
||||||
* Servlet API spec.
|
|
||||||
*
|
|
||||||
* see org.apache.catalina.servlets.CGIServlet
|
|
||||||
*/
|
|
||||||
if (len <= 0)
|
|
||||||
{
|
|
||||||
env.set(ENV_CONTENT_LENGTH, "");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
env.set(ENV_CONTENT_LENGTH, Integer.toString(len));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode PATH_INFO
|
* Decode PATH_INFO
|
||||||
@@ -383,6 +362,39 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
|||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the content length as string in the cgi specific format.
|
||||||
|
*
|
||||||
|
* CGI spec says CONTENT_LENGTH must be NULL ("") or undefined
|
||||||
|
* if there is no content, so we cannot put 0 or -1 in as per the
|
||||||
|
* Servlet API spec. Some CGI applications require a content
|
||||||
|
* length environment variable, which is not null or empty
|
||||||
|
* (e.g. mercurial). For those application the disallowEmptyResults
|
||||||
|
* parameter should be used.
|
||||||
|
*
|
||||||
|
* @param disallowEmptyResults {@code true} to return -1 instead of an empty string
|
||||||
|
*
|
||||||
|
* @return content length as cgi specific string
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static String createCGIContentLength(HttpServletRequest request, boolean disallowEmptyResults) {
|
||||||
|
String cgiContentLength = disallowEmptyResults ? "-1" : "";
|
||||||
|
|
||||||
|
String contentLength = request.getHeader("Content-Length");
|
||||||
|
if (!Strings.isNullOrEmpty(contentLength)) {
|
||||||
|
try {
|
||||||
|
long len = Long.parseLong(contentLength);
|
||||||
|
if (len > 0) {
|
||||||
|
cgiContentLength = String.valueOf(len);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
logger.warn("received request with invalid content-length header value: {}", contentLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cgiContentLength;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.Manager;
|
import sonia.scm.Manager;
|
||||||
import sonia.scm.ModelObject;
|
import sonia.scm.ModelObject;
|
||||||
|
import sonia.scm.group.Group;
|
||||||
|
|
||||||
import javax.ws.rs.core.GenericEntity;
|
import javax.ws.rs.core.GenericEntity;
|
||||||
import javax.ws.rs.core.Request;
|
import javax.ws.rs.core.Request;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -59,6 +63,27 @@ public class AbstractManagerResourceTest {
|
|||||||
abstractManagerResource.getAll(request, 0, 1, "x", true);
|
abstractManagerResource.getAll(request, 0, 1, "x", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@Test
|
||||||
|
public void testLocation() throws URISyntaxException {
|
||||||
|
URI base = new URI("https://scm.scm-manager.org/");
|
||||||
|
|
||||||
|
TestManagerResource resource = new TestManagerResource(manager);
|
||||||
|
when(uriInfo.getAbsolutePath()).thenReturn(base);
|
||||||
|
|
||||||
|
URI uri = resource.location(uriInfo, "special-group");
|
||||||
|
assertEquals(new URI("https://scm.scm-manager.org/groups/special-group"), uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLocationWithSpaces() throws URISyntaxException {
|
||||||
|
URI base = new URI("https://scm.scm-manager.org/");
|
||||||
|
|
||||||
|
TestManagerResource resource = new TestManagerResource(manager);
|
||||||
|
when(uriInfo.getAbsolutePath()).thenReturn(base);
|
||||||
|
|
||||||
|
URI uri = resource.location(uriInfo, "Scm Special Group");
|
||||||
|
assertEquals(new URI("https://scm.scm-manager.org/groups/Scm%20Special%20Group"), uri);
|
||||||
|
}**/
|
||||||
|
|
||||||
private class SimpleManagerResource extends AbstractManagerResource<Simple> {
|
private class SimpleManagerResource extends AbstractManagerResource<Simple> {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.it;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import com.sun.jersey.api.client.Client;
|
||||||
|
import com.sun.jersey.api.client.ClientResponse;
|
||||||
|
import com.sun.jersey.api.client.WebResource;
|
||||||
|
import org.eclipse.jgit.api.CommitCommand;
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||||
|
import org.eclipse.jgit.transport.PushResult;
|
||||||
|
import org.eclipse.jgit.transport.RemoteRefUpdate;
|
||||||
|
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
|
||||||
|
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||||
|
import org.junit.*;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import sonia.scm.repository.GitConfig;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
|
import sonia.scm.repository.client.api.RepositoryClientFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static sonia.scm.it.IntegrationTestUtil.*;
|
||||||
|
import static sonia.scm.it.RepositoryITUtil.createRepository;
|
||||||
|
import static sonia.scm.it.RepositoryITUtil.deleteRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration Tests for Git with non fast-forward pushes.
|
||||||
|
*/
|
||||||
|
@Ignore
|
||||||
|
public class GitNonFastForwardITCase {
|
||||||
|
|
||||||
|
private static final RepositoryClientFactory REPOSITORY_CLIENT_FACTORY = new RepositoryClientFactory();
|
||||||
|
|
||||||
|
private Client apiClient;
|
||||||
|
private Repository repository;
|
||||||
|
private File workingCopy;
|
||||||
|
private Git git;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder tempFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void createAndCloneTestRepository() throws IOException, GitAPIException {
|
||||||
|
// apiClient = createAdminClient();
|
||||||
|
Repository testRepository = RepositoryTestData.createHeartOfGold("git");
|
||||||
|
// this.repository = createRepository(apiClient, testRepository);
|
||||||
|
this.workingCopy = tempFolder.newFolder();
|
||||||
|
|
||||||
|
// String url = repository.createUrl(BASE_URL);
|
||||||
|
// this.git = clone(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void removeTestRepository() {
|
||||||
|
// deleteRepository(apiClient, repository.getId());
|
||||||
|
apiClient.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the normal behaviour (non fast-forward is allowed), is restored after the tests are executed.
|
||||||
|
*/
|
||||||
|
@AfterClass
|
||||||
|
public static void allowNonFastForward() {
|
||||||
|
setNonFastForwardDisallowed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGitPushAmendWithoutForce() throws IOException, GitAPIException {
|
||||||
|
setNonFastForwardDisallowed(false);
|
||||||
|
|
||||||
|
addTestFileToWorkingCopyAndCommit("a");
|
||||||
|
pushAndAssert(false, Status.OK);
|
||||||
|
|
||||||
|
addTestFileToWorkingCopyAndCommitAmend("c");
|
||||||
|
pushAndAssert(false, Status.REJECTED_NONFASTFORWARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGitPushAmendWithForce() throws IOException, GitAPIException {
|
||||||
|
setNonFastForwardDisallowed(false);
|
||||||
|
|
||||||
|
addTestFileToWorkingCopyAndCommit("a");
|
||||||
|
pushAndAssert(false, Status.OK);
|
||||||
|
|
||||||
|
addTestFileToWorkingCopyAndCommitAmend("c");
|
||||||
|
pushAndAssert(true, Status.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGitPushAmendForceWithDisallowNonFastForward() throws GitAPIException, IOException {
|
||||||
|
setNonFastForwardDisallowed(true);
|
||||||
|
|
||||||
|
addTestFileToWorkingCopyAndCommit("a");
|
||||||
|
pushAndAssert(false, Status.OK);
|
||||||
|
|
||||||
|
addTestFileToWorkingCopyAndCommitAmend("c");
|
||||||
|
pushAndAssert(true, Status.REJECTED_OTHER_REASON);
|
||||||
|
|
||||||
|
setNonFastForwardDisallowed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CredentialsProvider createCredentialProvider() {
|
||||||
|
return new UsernamePasswordCredentialsProvider(
|
||||||
|
IntegrationTestUtil.ADMIN_USERNAME, IntegrationTestUtil.ADMIN_PASSWORD
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Git clone(String url) throws GitAPIException {
|
||||||
|
return Git.cloneRepository()
|
||||||
|
.setDirectory(workingCopy)
|
||||||
|
.setURI(url)
|
||||||
|
.setCredentialsProvider(createCredentialProvider())
|
||||||
|
.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTestFileToWorkingCopyAndCommit(String name) throws IOException, GitAPIException {
|
||||||
|
addTestFile(name);
|
||||||
|
prepareCommit()
|
||||||
|
.setMessage("added ".concat(name))
|
||||||
|
.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTestFile(String name) throws IOException, GitAPIException {
|
||||||
|
String filename = name.concat(".txt");
|
||||||
|
Files.write(name, new File(workingCopy, filename), Charsets.UTF_8);
|
||||||
|
git.add().addFilepattern(filename).call();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommitCommand prepareCommit() {
|
||||||
|
return git.commit()
|
||||||
|
.setAuthor(IntegrationTestUtil.AUTHOR.getName(), IntegrationTestUtil.AUTHOR.getMail());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pushAndAssert(boolean force, Status expectedStatus) throws GitAPIException {
|
||||||
|
Iterable<PushResult> results = push(force);
|
||||||
|
assertStatus(results, expectedStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Iterable<PushResult> push(boolean force) throws GitAPIException {
|
||||||
|
return git.push()
|
||||||
|
.setRemote("origin")
|
||||||
|
.add("master")
|
||||||
|
.setForce(force)
|
||||||
|
.setCredentialsProvider(createCredentialProvider())
|
||||||
|
.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertStatus(Iterable<PushResult> results, Status expectedStatus) {
|
||||||
|
for ( PushResult pushResult : results ) {
|
||||||
|
assertStatus(pushResult, expectedStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertStatus(PushResult pushResult, Status expectedStatus) {
|
||||||
|
for ( RemoteRefUpdate remoteRefUpdate : pushResult.getRemoteUpdates() ) {
|
||||||
|
assertEquals(expectedStatus, remoteRefUpdate.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTestFileToWorkingCopyAndCommitAmend(String name) throws IOException, GitAPIException {
|
||||||
|
addTestFile(name);
|
||||||
|
prepareCommit()
|
||||||
|
.setMessage("amend commit, because of missing ".concat(name))
|
||||||
|
.setAmend(true)
|
||||||
|
.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setNonFastForwardDisallowed(boolean nonFastForwardDisallowed) {
|
||||||
|
/*Client adminClient = createAdminClient();
|
||||||
|
try {
|
||||||
|
WebResource resource = createResource(adminClient, "config/repositories/git");
|
||||||
|
GitConfig config = resource.get(GitConfig.class);
|
||||||
|
|
||||||
|
assertNotNull(config);
|
||||||
|
config.setNonFastForwardDisallowed(nonFastForwardDisallowed);
|
||||||
|
|
||||||
|
ClientResponse response = resource.post(ClientResponse.class, config);
|
||||||
|
assertNotNull(response);
|
||||||
|
assertEquals(201, response.getStatus());
|
||||||
|
} finally {
|
||||||
|
adminClient.destroy();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package sonia.scm.web.cgi;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link DefaultCGIExecutor}.
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class DefaultCGIExecutorTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpServletRequest request;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateCGIContentLength() {
|
||||||
|
when(request.getHeader("Content-Length")).thenReturn("42");
|
||||||
|
assertEquals("42", DefaultCGIExecutor.createCGIContentLength(request, false));
|
||||||
|
assertEquals("42", DefaultCGIExecutor.createCGIContentLength(request, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateCGIContentLengthWithZeroLength() {
|
||||||
|
when(request.getHeader("Content-Length")).thenReturn("0");
|
||||||
|
assertEquals("", DefaultCGIExecutor.createCGIContentLength(request, false));
|
||||||
|
assertEquals("-1", DefaultCGIExecutor.createCGIContentLength(request, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateCGIContentLengthWithoutContentLengthHeader() {
|
||||||
|
assertEquals("", DefaultCGIExecutor.createCGIContentLength(request, false));
|
||||||
|
assertEquals("-1", DefaultCGIExecutor.createCGIContentLength(request, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateCGIContentLengthWithLengthThatExeedsInteger() {
|
||||||
|
when(request.getHeader("Content-Length")).thenReturn("6314297259");
|
||||||
|
assertEquals("6314297259", DefaultCGIExecutor.createCGIContentLength(request, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateCGIContentLengthWithNonNumberHeader() {
|
||||||
|
when(request.getHeader("Content-Length")).thenReturn("abc");
|
||||||
|
assertEquals("", DefaultCGIExecutor.createCGIContentLength(request, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user