merge with branch 1.x

This commit is contained in:
Sebastian Sdorra
2019-01-29 09:42:03 +01:00
53 changed files with 3802 additions and 242 deletions

View 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
```

View 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
```

View 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
```

View 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
```

View 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
```

131
pom.xml
View File

@@ -376,6 +376,70 @@
<version>3.10.0</version>
<scope>test</scope>
</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>
</dependencyManagement>
@@ -418,10 +482,11 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<version>3.0.0-M1</version>
<executions>
<execution>
<id>enforce-java</id>
@@ -443,11 +508,31 @@
<requireMavenVersion>
<version>[3.1,)</version>
</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>
<fail>true</fail>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>extra-enforcer-rules</artifactId>
<version>1.0-beta-7</version>
</dependency>
</dependencies>
</plugin>
<plugin>
@@ -636,9 +721,15 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.2</version>
<configuration>
<reportPlugins>
<version>3.7</version>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -669,19 +760,13 @@
<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>
</plugins>
</build>
</reporting>
<profiles>
@@ -726,17 +811,9 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<doclet>org.jboss.apiviz.APIviz</doclet>
<docletArtifact>
<groupId>org.jboss.apiviz</groupId>
<artifactId>apiviz</artifactId>
<version>1.3.2.GA</version>
</docletArtifact>
<additionalparam>
-sourceclasspath ${project.build.outputDirectory}
-nopackagediagram
</additionalparam>
<failOnError>false</failOnError>
</configuration>
</plugin>
@@ -770,8 +847,8 @@
<junit.version>5.2.0</junit.version>
<!-- logging libraries -->
<slf4j.version>1.7.22</slf4j.version>
<logback.version>1.1.10</logback.version>
<slf4j.version>1.7.25</slf4j.version>
<logback.version>1.2.3</logback.version>
<servlet.version>3.0.1</servlet.version>
<jaxrs.version>2.0.1</jaxrs.version>
@@ -780,6 +857,7 @@
<enunciate.version>2.11.1</enunciate.version>
<jackson.version>2.8.6</jackson.version>
<guice.version>4.0</guice.version>
<jaxb.version>2.3.0</jaxb.version>
<!-- event bus -->
<legman.version>1.4.2</legman.version>
@@ -792,9 +870,9 @@
<ssp.version>1.1.0</ssp.version>
<shiro.version>1.4.0</shiro.version>
<!-- repostitory libraries -->
<jgit.version>v4.5.2.201704071617-r-scm1</jgit.version>
<svnkit.version>1.8.15-scm1</svnkit.version>
<!-- repository libraries -->
<jgit.version>v4.5.3.201708160445-r-scm1</jgit.version>
<svnkit.version>1.9.0-scm3</svnkit.version>
<!-- util libraries -->
<guava.version>26.0-jre</guava.version>
@@ -806,6 +884,7 @@
<!-- build properties -->
<project.build.javaLevel>1.8</project.build.javaLevel>
<project.test.javaLevel>1.8</project.test.javaLevel>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<netbeans.hint.license>SCM-BSD</netbeans.hint.license>
<jdk.classifier />

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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());
}
}

View File

@@ -47,6 +47,7 @@
<groupId>org.slf4j</groupId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
@@ -143,6 +144,28 @@
<version>${legman.version}</version>
</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 -->
<dependency>
@@ -193,6 +216,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<useStandardDocletOptions>true</useStandardDocletOptions>
<charset>${project.build.sourceEncoding}</charset>
@@ -216,6 +240,20 @@
<link>http://www.slf4j.org/api/</link>
<link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link>
</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>
</plugin>

View File

@@ -82,7 +82,6 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -55,8 +55,10 @@ public class GitConfig extends RepositoryConfig {
@XmlElement(name = "gc-expression")
private String gcExpression;
public String getGcExpression()
{
@XmlElement(name = "disallow-non-fast-forward")
private boolean nonFastForwardDisallowed;
public String getGcExpression() {
return gcExpression;
}
@@ -64,6 +66,14 @@ public class GitConfig extends RepositoryConfig {
this.gcExpression = gcExpression;
}
public boolean isNonFastForwardDisallowed() {
return nonFastForwardDisallowed;
}
public void setNonFastForwardDisallowed(boolean nonFastForwardDisallowed) {
this.nonFastForwardDisallowed = nonFastForwardDisallowed;
}
@Override
@XmlTransient // Only for permission checks, don't serialize to XML
public String getId() {

View File

@@ -68,10 +68,13 @@ public class GitHookTagProvider implements HookTagProvider {
if (Strings.isNullOrEmpty(tag)){
logger.debug("received ref name {} is not a tag", refName);
} else if (rc.getType() == ReceiveCommand.Type.CREATE) {
createdTagBuilder.add(new Tag(tag, GitUtil.getId(rc.getNewId())));
} else if (rc.getType() == ReceiveCommand.Type.DELETE){
deletedTagBuilder.add(new Tag(tag, GitUtil.getId(rc.getOldId())));
} else if (isCreate(rc)) {
createdTagBuilder.add(createTagFromNewId(rc, tag));
} else if (isDelete(rc)){
deletedTagBuilder.add(createTagFromOldId(rc, tag));
} else if (isUpdate(rc)) {
createdTagBuilder.add(createTagFromNewId(rc, tag));
deletedTagBuilder.add(createTagFromOldId(rc, tag));
}
}
@@ -79,6 +82,26 @@ public class GitHookTagProvider implements HookTagProvider {
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
public List<Tag> getCreatedTags() {
return createdTags;

View File

@@ -35,79 +35,63 @@ package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.spi.HookEventFacade;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.http.HttpServletRequest;
//~--- JDK imports ------------------------------------------------------------
/**
* GitReceivePackFactory creates {@link ReceivePack} objects and assigns the required
* Hook components.
*
* @author Sebastian Sdorra
*/
public class GitReceivePackFactory
implements ReceivePackFactory<HttpServletRequest>
public class GitReceivePackFactory implements ReceivePackFactory<HttpServletRequest>
{
/**
* Constructs ...
*
*
*
* @param hookEventFacade
* @param handler
*/
private final GitRepositoryHandler handler;
private ReceivePackFactory wrapped;
private final GitReceiveHook hook;
@Inject
public GitReceivePackFactory(HookEventFacade hookEventFacade,
GitRepositoryHandler handler)
{
hook = new GitReceiveHook(hookEventFacade, handler);
public GitReceivePackFactory(GitRepositoryHandler handler, HookEventFacade hookEventFacade) {
this.handler = handler;
this.hook = new GitReceiveHook(hookEventFacade, handler);
this.wrapped = new DefaultReceivePackFactory();
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param request
* @param repository
*
* @return
*
* @throws ServiceNotAuthorizedException
* @throws ServiceNotEnabledException
*/
@Override
public ReceivePack create(HttpServletRequest request, Repository repository)
throws ServiceNotEnabledException, ServiceNotAuthorizedException
{
ReceivePack rpack = defaultFactory.create(request, repository);
throws ServiceNotEnabledException, ServiceNotAuthorizedException {
ReceivePack receivePack = wrapped.create(request, repository);
receivePack.setAllowNonFastForwards(isNonFastForwardAllowed());
rpack.setPreReceiveHook(hook);
rpack.setPostReceiveHook(hook);
receivePack.setPreReceiveHook(hook);
receivePack.setPostReceiveHook(hook);
// 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 */
private DefaultReceivePackFactory defaultFactory =
new DefaultReceivePackFactory();
/** Field description */
private GitReceiveHook hook;
@VisibleForTesting
void setWrapped(ReceivePackFactory wrapped) {
this.wrapped = wrapped;
}
}

View File

@@ -32,20 +32,23 @@
package sonia.scm.repository.api;
import com.google.common.collect.Lists;
import java.util.List;
import org.eclipse.jgit.lib.ObjectId;
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.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.OngoingStubbing;
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}.
*
@@ -54,6 +57,11 @@ import sonia.scm.repository.Tag;
@RunWith(MockitoJUnitRunner.class)
public class GitHookTagProviderTest {
private static final String ZERO = ObjectId.zeroId().getName();
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Mock
private ReceiveCommand command;
@@ -73,7 +81,7 @@ public class GitHookTagProviderTest {
@Test
public void testGetCreatedTags() {
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());
assertThat(provider.getDeletedTags(), empty());
@@ -85,7 +93,7 @@ public class GitHookTagProviderTest {
@Test
public void testGetDeletedTags() {
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());
assertTag("1.0.0", revision, provider.getDeletedTags());
@@ -97,12 +105,25 @@ public class GitHookTagProviderTest {
@Test
public void testWithBranch(){
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.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){
assertNotNull(tags);
assertFalse(tags.isEmpty());
@@ -112,18 +133,11 @@ public class GitHookTagProviderTest {
assertEquals(revision, tag.getRevision());
}
private GitHookTagProvider createProvider(ReceiveCommand.Type type, String ref, String id){
OngoingStubbing<ObjectId> ongoing;
if (type == ReceiveCommand.Type.CREATE){
ongoing = when(command.getNewId());
} else {
ongoing = when(command.getOldId());
}
ongoing.thenReturn(ObjectId.fromString(id));
private GitHookTagProvider createProvider(ReceiveCommand.Type type, String ref, String newId, String oldId){
when(command.getNewId()).thenReturn(ObjectId.fromString(newId));
when(command.getOldId()).thenReturn(ObjectId.fromString(oldId));
when(command.getType()).thenReturn(type);
when(command.getRefName()).thenReturn(ref);
return new GitHookTagProvider(commands);
}

View File

@@ -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());
}
}

View File

@@ -20,7 +20,7 @@
<dependency>
<groupId>com.aragost.javahg</groupId>
<artifactId>javahg</artifactId>
<version>0.8-scm1</version>
<version>0.13-java7</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
@@ -81,7 +81,6 @@
</executions>
</plugin>
</plugins>
</build>

View File

@@ -58,6 +58,14 @@ public class HgConfig extends RepositoryConfig
//~--- 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
*
@@ -124,6 +132,14 @@ public class HgConfig extends RepositoryConfig
return useOptimizedBytecode;
}
public boolean isDisableHookSSLValidation() {
return disableHookSSLValidation;
}
public boolean isEnableHttpPostArgs() {
return enableHttpPostArgs;
}
/**
* Method description
*
@@ -194,6 +210,10 @@ public class HgConfig extends RepositoryConfig
this.showRevisionInId = showRevisionInId;
}
public void setEnableHttpPostArgs(boolean enableHttpPostArgs) {
this.enableHttpPostArgs = enableHttpPostArgs;
}
/**
* Method description
*
@@ -205,6 +225,10 @@ public class HgConfig extends RepositoryConfig
this.useOptimizedBytecode = useOptimizedBytecode;
}
public void setDisableHookSSLValidation(boolean disableHookSSLValidation) {
this.disableHookSSLValidation = disableHookSSLValidation;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@@ -225,10 +249,11 @@ public class HgConfig extends RepositoryConfig
/** Field description */
private boolean showRevisionInId = false;
@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;
}
private boolean enableHttpPostArgs = false;
/**
* disable validation of ssl certificates for mercurial hook
* @see <a href="https://goo.gl/zH5eY8">Issue 959</a>
*/
private boolean disableHookSSLValidation = false;
}

View File

@@ -56,15 +56,20 @@ import sonia.scm.web.cgi.CGIExecutor;
import sonia.scm.web.cgi.CGIExecutorFactory;
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.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.Base64;
import java.util.Enumeration;
/**
*
@@ -74,6 +79,8 @@ import java.util.Enumeration;
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
{
private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY";
/** Field description */
public static final String ENV_REPOSITORY_NAME = "REPO_NAME";
@@ -83,6 +90,8 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
/** Field description */
public static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
/** Field description */
public static final String ENV_SESSION_PREFIX = "SCM_";
@@ -268,9 +277,20 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
directory.getAbsolutePath());
// 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-
HgEnvironment.prepareEnvironment(
executor.getEnvironment().asMutableMap(),
environment,
handler,
hookManager,
request

View File

@@ -33,13 +33,23 @@
package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.Repository;
import sonia.scm.repository.spi.ScmProviderHttpServlet;
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 java.util.Set;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 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");
public HgPermissionFilter(ScmConfiguration configuration, ScmProviderHttpServlet delegate)
private final HgRepositoryHandler repositoryHandler;
public HgPermissionFilter(ScmConfiguration configuration, ScmProviderHttpServlet delegate, HgRepositoryHandler repositoryHandler)
{
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
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;
}
}

View File

@@ -12,10 +12,12 @@ import javax.inject.Inject;
public class HgPermissionFilterFactory implements ScmProviderHttpServletDecoratorFactory {
private final ScmConfiguration configuration;
private final HgRepositoryHandler repositoryHandler;
@Inject
public HgPermissionFilterFactory(ScmConfiguration configuration) {
public HgPermissionFilterFactory(ScmConfiguration configuration, HgRepositoryHandler repositoryHandler) {
this.configuration = configuration;
this.repositoryHandler = repositoryHandler;
}
@Override
@@ -25,6 +27,6 @@ public class HgPermissionFilterFactory implements ScmProviderHttpServletDecorato
@Override
public ScmProviderHttpServlet createDecorator(ScmProviderHttpServlet delegate) {
return new HgPermissionFilter(configuration, delegate);
return new HgPermissionFilter(configuration, delegate,repositoryHandler);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -37,7 +37,22 @@ from collections import defaultdict
from mercurial import cmdutil,util
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>'
@@ -201,7 +216,7 @@ class File_Printer:
description = 'n/a'
if not self.disableLastCommit:
linkrev = self.repo[file.linkrev()]
date = '%d %d' % util.parsedate(linkrev.date())
date = '%d %d' % _parsedate(linkrev.date())
description = linkrev.description()
format = '%s %i %s %s\n'
if self.transport:

View File

@@ -36,7 +36,11 @@ from mercurial.hgweb import hgweb, wsgicgi
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', 'allow_read', '*')
@@ -45,7 +49,13 @@ u.setconfig('web', 'allow_push', '*')
u.setconfig('hooks', 'changegroup.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'])
application = hgweb(r)
wsgicgi.launch(application)

View File

@@ -47,7 +47,7 @@ def printMessages(ui, msgs):
for line in msgs:
if line.startswith("_e") or line.startswith("_n"):
line = line[2:];
ui.warn(line);
ui.warn('%s\n' % line.rstrip())
def callHookUrl(ui, repo, hooktype, node):
abort = True
@@ -79,8 +79,10 @@ def callHookUrl(ui, repo, hooktype, node):
printMessages(ui, msg.splitlines(True))
else:
ui.warn( "ERROR: scm-hook failed with an unknown error\n" )
ui.traceback()
except ValueError:
ui.warn( "scm-hook failed with an exception\n" )
ui.traceback()
return abort
def callback(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):

View File

@@ -1,7 +1,8 @@
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"
file_add = "a {file_add}\n"
file_mod = "m {file_mod}\n"
file_del = "d {file_del}\n"
extra = "{key}={value|stringescape},"
footer = "%{pattern}"

View File

@@ -31,18 +31,29 @@
package sonia.scm.web;
import javax.servlet.http.HttpServletRequest;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.Mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.HgConfig;
import sonia.scm.repository.HgRepositoryHandler;
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}.
*
@@ -60,9 +71,33 @@ public class HgPermissionFilterTest {
@Mock
private RepositoryProvider repositoryProvider;
@Mock
private HgRepositoryHandler hgRepositoryHandler;
private WireProtocolRequestMockFactory wireProtocol = new WireProtocolRequestMockFactory("/scm/hg/repo");
@InjectMocks
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)}.
*/
@@ -81,8 +116,121 @@ public class HgPermissionFilterTest {
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) {
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);
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));
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -306,8 +306,8 @@
</profiles>
<properties>
<commons.daemon.version>1.0.15</commons.daemon.version>
<commons.daemon.native.version>1.0.15.1</commons.daemon.native.version>
<commons.daemon.version>1.1.0</commons.daemon.version>
<commons.daemon.native.version>1.1.0</commons.daemon.native.version>
<exploded.directory>${project.build.directory}/appassembler/commons-daemon/scm-server</exploded.directory>
</properties>

View File

@@ -146,11 +146,13 @@
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-servlet-initializer</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-validator-provider-11</artifactId>
<version>${resteasy.version}</version>
</dependency>
<!-- injection -->
<dependency>
@@ -172,7 +174,6 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
@@ -187,18 +188,26 @@
<version>${slf4j.version}</version>
</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 -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<!--
@@ -230,14 +239,6 @@
</exclusions>
</dependency>
<!-- fix version conflict -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2.6</version>
</dependency>
<!-- template engine -->
<dependency>

View File

@@ -35,6 +35,8 @@ package sonia.scm.api.rest.resources;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.net.UrlEscapers;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
@@ -45,7 +47,6 @@ import sonia.scm.ModelObject;
import sonia.scm.PageResult;
import sonia.scm.api.rest.RestExceptionResult;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.util.Util;
import javax.ws.rs.core.CacheControl;
@@ -63,6 +64,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.net.URI;
//~--- JDK imports ------------------------------------------------------------
@@ -139,11 +141,7 @@ public abstract class AbstractManagerResource<T extends ModelObject> {
manager.create(item);
String id = getId(item);
id = HttpUtil.encode(id);
response = Response.created(
uriInfo.getAbsolutePath().resolve(
getPathPart().concat("/").concat(id))).build();
response = Response.created(location(uriInfo, id)).build();
}
catch (AuthorizationException ex)
{
@@ -159,6 +157,12 @@ public abstract class AbstractManagerResource<T extends ModelObject> {
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
*
@@ -247,7 +251,7 @@ public abstract class AbstractManagerResource<T extends ModelObject> {
*/
public Response get(Request request, String id)
{
Response response = null;
Response response;
T item = manager.get(id);
if (item != null)

View File

@@ -35,6 +35,7 @@ package sonia.scm.web.cgi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.io.ByteStreams;
@@ -139,12 +140,6 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
apendOsEnvironment(env);
}
// workaround for mercurial 2.1
if (isContentLengthWorkaround())
{
env.set(ENV_CONTENT_LENGTH, Integer.toString(request.getContentLength()));
}
if (workDirectory == null)
{
workDirectory = command.getParentFile();
@@ -304,26 +299,10 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
String uri = HttpUtil.removeMatrixParameter(request.getRequestURI());
String scriptName = uri.substring(0, uri.length() - pathInfo.length());
String scriptPath = context.getRealPath(scriptName);
int len = request.getContentLength();
EnvList env = new EnvList();
env.set(ENV_AUTH_TYPE, request.getAuthType());
/**
* 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));
}
env.set(ENV_CONTENT_LENGTH, createCGIContentLength(request, contentLengthWorkaround));
/**
* Decode PATH_INFO
@@ -383,6 +362,39 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
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
*

View File

@@ -9,13 +9,17 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.Manager;
import sonia.scm.ModelObject;
import sonia.scm.group.Group;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Request;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Comparator;
import static java.util.Collections.emptyList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
@@ -59,6 +63,27 @@ public class AbstractManagerResourceTest {
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> {

View File

@@ -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();
}*/
}
}

View File

@@ -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));
}
}