From b7b3bb815dd0fd7fd8138a46fab49f9129a81fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 2 Jul 2018 12:39:40 +0200 Subject: [PATCH 001/186] start feature branch From 717f121a3effe0c04990c9c5cbcbb004d946980e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 2 Jul 2018 13:50:28 +0200 Subject: [PATCH 002/186] update .hgignore --- .hgignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.hgignore b/.hgignore index 6ff185f670..f9b5452045 100644 --- a/.hgignore +++ b/.hgignore @@ -29,3 +29,8 @@ Desktop DF$ # jrebel rebel.xml \.pyc +# ui +scm-ui/node-modules/ +scm-ui/yarn.lock +scm-ui/.gitignore +scm-ui/package-lock.json From 61eeadcd84d1dceeb95d281abcb653cac0ac1fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 2 Jul 2018 13:51:24 +0200 Subject: [PATCH 003/186] update .hgignore --- .hgignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.hgignore b/.hgignore index f9b5452045..71fbaa8735 100644 --- a/.hgignore +++ b/.hgignore @@ -30,7 +30,7 @@ Desktop DF$ rebel.xml \.pyc # ui -scm-ui/node-modules/ +scm-ui/node_modules/ scm-ui/yarn.lock scm-ui/.gitignore scm-ui/package-lock.json From cdbdbf40cab05135e10a9bbb3e0753a5831b8e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 2 Jul 2018 14:50:13 +0200 Subject: [PATCH 004/186] initial import for scm ui --- .hgignore | 3 +- scm-ui/package.json | 27 +++++++ scm-ui/public/favicon.ico | Bin 0 -> 3870 bytes scm-ui/public/index.html | 40 ++++++++++ scm-ui/public/manifest.json | 15 ++++ scm-ui/src/App.js | 20 +++++ scm-ui/src/Main.js | 36 +++++++++ scm-ui/src/Navigation.js | 52 +++++++++++++ scm-ui/src/containers/Page.js | 3 + scm-ui/src/createReduxStore.js | 22 ++++++ scm-ui/src/index.js | 32 ++++++++ scm-ui/src/modules/page.js | 3 + scm-ui/src/registerServiceWorker.js | 117 ++++++++++++++++++++++++++++ scm.iml | 16 ++++ 14 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 scm-ui/package.json create mode 100644 scm-ui/public/favicon.ico create mode 100644 scm-ui/public/index.html create mode 100644 scm-ui/public/manifest.json create mode 100644 scm-ui/src/App.js create mode 100644 scm-ui/src/Main.js create mode 100644 scm-ui/src/Navigation.js create mode 100644 scm-ui/src/containers/Page.js create mode 100644 scm-ui/src/createReduxStore.js create mode 100644 scm-ui/src/index.js create mode 100644 scm-ui/src/modules/page.js create mode 100644 scm-ui/src/registerServiceWorker.js create mode 100644 scm.iml diff --git a/.hgignore b/.hgignore index 71fbaa8735..4e95a4bedc 100644 --- a/.hgignore +++ b/.hgignore @@ -30,7 +30,8 @@ Desktop DF$ rebel.xml \.pyc # ui -scm-ui/node_modules/ +scm-ui/node_modules scm-ui/yarn.lock scm-ui/.gitignore scm-ui/package-lock.json +node_modules diff --git a/scm-ui/package.json b/scm-ui/package.json new file mode 100644 index 0000000000..c56f88f9e1 --- /dev/null +++ b/scm-ui/package.json @@ -0,0 +1,27 @@ +{ + "name": "scm-ui", + "version": "0.1.0", + "private": true, + "dependencies": { + "ces-theme": "https://github.com/cloudogu/ces-theme.git", + "classnames": "^2.2.5", + "react": "^16.4.1", + "react-dom": "^16.4.1", + "react-jss": "^8.6.0", + "react-redux": "^5.0.7", + "react-scripts": "1.1.4", + "redux": "^4.0.0", + "redux-logger": "^3.0.6", + "redux-thunk": "^2.3.0", + "history": "^4.7.2", + "react-router-dom": "^4.3.1", + "react-router-redux": "^5.0.0-alpha.9", + "redux-devtools-extension": "^2.13.5" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} diff --git a/scm-ui/public/favicon.ico b/scm-ui/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= z!sii`1giyH7EKL_+Wi0ab<)&E_0KD!3Rp2^HNB*K2@PHCs4PWSA32*-^7d{9nH2_E zmC{C*N*)(vEF1_aMamw2A{ZH5aIDqiabnFdJ|y0%aS|64E$`s2ccV~3lR!u<){eS` z#^Mx6o(iP1Ix%4dv`t@!&Za-K@mTm#vadc{0aWDV*_%EiGK7qMC_(`exc>-$Gb9~W!w_^{*pYRm~G zBN{nA;cm^w$VWg1O^^<6vY`1XCD|s_zv*g*5&V#wv&s#h$xlUilPe4U@I&UXZbL z0)%9Uj&@yd03n;!7do+bfixH^FeZ-Ema}s;DQX2gY+7g0s(9;`8GyvPY1*vxiF&|w z>!vA~GA<~JUqH}d;DfBSi^IT*#lrzXl$fNpq0_T1tA+`A$1?(gLb?e#0>UELvljtQ zK+*74m0jn&)5yk8mLBv;=@}c{t0ztT<v;Avck$S6D`Z)^c0(jiwKhQsn|LDRY&w(Fmi91I7H6S;b0XM{e zXp0~(T@k_r-!jkLwd1_Vre^v$G4|kh4}=Gi?$AaJ)3I+^m|Zyj#*?Kp@w(lQdJZf4 z#|IJW5z+S^e9@(6hW6N~{pj8|NO*>1)E=%?nNUAkmv~OY&ZV;m-%?pQ_11)hAr0oAwILrlsGawpxx4D43J&K=n+p3WLnlDsQ$b(9+4 z?mO^hmV^F8MV{4Lx>(Q=aHhQ1){0d*(e&s%G=i5rq3;t{JC zmgbn5Nkl)t@fPH$v;af26lyhH!k+#}_&aBK4baYPbZy$5aFx4}ka&qxl z$=Rh$W;U)>-=S-0=?7FH9dUAd2(q#4TCAHky!$^~;Dz^j|8_wuKc*YzfdAht@Q&ror?91Dm!N03=4=O!a)I*0q~p0g$Fm$pmr$ zb;wD;STDIi$@M%y1>p&_>%?UP($15gou_ue1u0!4(%81;qcIW8NyxFEvXpiJ|H4wz z*mFT(qVx1FKufG11hByuX%lPk4t#WZ{>8ka2efjY`~;AL6vWyQKpJun2nRiZYDij$ zP>4jQXPaP$UC$yIVgGa)jDV;F0l^n(V=HMRB5)20V7&r$jmk{UUIe zVjKroK}JAbD>B`2cwNQ&GDLx8{pg`7hbA~grk|W6LgiZ`8y`{Iq0i>t!3p2}MS6S+ zO_ruKyAElt)rdS>CtF7j{&6rP-#c=7evGMt7B6`7HG|-(WL`bDUAjyn+k$mx$CH;q2Dz4x;cPP$hW=`pFfLO)!jaCL@V2+F)So3}vg|%O*^T1j>C2lx zsURO-zIJC$^$g2byVbRIo^w>UxK}74^TqUiRR#7s_X$e)$6iYG1(PcW7un-va-S&u zHk9-6Zn&>T==A)lM^D~bk{&rFzCi35>UR!ZjQkdSiNX*-;l4z9j*7|q`TBl~Au`5& z+c)*8?#-tgUR$Zd%Q3bs96w6k7q@#tUn`5rj+r@_sAVVLqco|6O{ILX&U-&-cbVa3 zY?ngHR@%l{;`ri%H*0EhBWrGjv!LE4db?HEWb5mu*t@{kv|XwK8?npOshmzf=vZA@ zVSN9sL~!sn?r(AK)Q7Jk2(|M67Uy3I{eRy z_l&Y@A>;vjkWN5I2xvFFTLX0i+`{qz7C_@bo`ZUzDugfq4+>a3?1v%)O+YTd6@Ul7 zAfLfm=nhZ`)P~&v90$&UcF+yXm9sq!qCx3^9gzIcO|Y(js^Fj)Rvq>nQAHI92ap=P z10A4@prk+AGWCb`2)dQYFuR$|H6iDE8p}9a?#nV2}LBCoCf(Xi2@szia7#gY>b|l!-U`c}@ zLdhvQjc!BdLJvYvzzzngnw51yRYCqh4}$oRCy-z|v3Hc*d|?^Wj=l~18*E~*cR_kU z{XsxM1i{V*4GujHQ3DBpl2w4FgFR48Nma@HPgnyKoIEY-MqmMeY=I<%oG~l!f<+FN z1ZY^;10j4M4#HYXP zw5eJpA_y(>uLQ~OucgxDLuf}fVs272FaMxhn4xnDGIyLXnw>Xsd^J8XhcWIwIoQ9} z%FoSJTAGW(SRGwJwb=@pY7r$uQRK3Zd~XbxU)ts!4XsJrCycrWSI?e!IqwqIR8+Jh zlRjZ`UO1I!BtJR_2~7AbkbSm%XQqxEPkz6BTGWx8e}nQ=w7bZ|eVP4?*Tb!$(R)iC z9)&%bS*u(lXqzitAN)Oo=&Ytn>%Hzjc<5liuPi>zC_nw;Z0AE3Y$Jao_Q90R-gl~5 z_xAb2J%eArrC1CN4G$}-zVvCqF1;H;abAu6G*+PDHSYFx@Tdbfox*uEd3}BUyYY-l zTfEsOqsi#f9^FoLO;ChK<554qkri&Av~SIM*{fEYRE?vH7pTAOmu2pz3X?Wn*!ROX ztd54huAk&mFBemMooL33RV-*1f0Q3_(7hl$<#*|WF9P!;r;4_+X~k~uKEqdzZ$5Al zV63XN@)j$FN#cCD;ek1R#l zv%pGrhB~KWgoCj%GT?%{@@o(AJGt*PG#l3i>lhmb_twKH^EYvacVY-6bsCl5*^~L0 zonm@lk2UvvTKr2RS%}T>^~EYqdL1q4nD%0n&Xqr^cK^`J5W;lRRB^R-O8b&HENO||mo0xaD+S=I8RTlIfVgqN@SXDr2&-)we--K7w= zJVU8?Z+7k9dy;s;^gDkQa`0nz6N{T?(A&Iz)2!DEecLyRa&FI!id#5Z7B*O2=PsR0 zEvc|8{NS^)!d)MDX(97Xw}m&kEO@5jqRaDZ!+%`wYOI<23q|&js`&o4xvjP7D_xv@ z5hEwpsp{HezI9!~6O{~)lLR@oF7?J7i>1|5a~UuoN=q&6N}EJPV_GD`&M*v8Y`^2j zKII*d_@Fi$+i*YEW+Hbzn{iQk~yP z>7N{S4)r*!NwQ`(qcN#8SRQsNK6>{)X12nbF`*7#ecO7I)Q$uZsV+xS4E7aUn+U(K baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/scm-ui/public/index.html b/scm-ui/public/index.html new file mode 100644 index 0000000000..ed0ebafa1b --- /dev/null +++ b/scm-ui/public/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + React App + + + +
+ + + diff --git a/scm-ui/public/manifest.json b/scm-ui/public/manifest.json new file mode 100644 index 0000000000..ef19ec243e --- /dev/null +++ b/scm-ui/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/scm-ui/src/App.js b/scm-ui/src/App.js new file mode 100644 index 0000000000..79083246bd --- /dev/null +++ b/scm-ui/src/App.js @@ -0,0 +1,20 @@ +import React, { Component } from 'react'; +import Navigation from './Navigation'; +import Main from './Main'; +import {withRouter} from 'react-router-dom'; +import 'ces-theme/dist/css/ces.css'; + + + +class App extends Component { + render() { + return ( +
+ +
+
+ ); + } +} + +export default withRouter(App); diff --git a/scm-ui/src/Main.js b/scm-ui/src/Main.js new file mode 100644 index 0000000000..068bc38a01 --- /dev/null +++ b/scm-ui/src/Main.js @@ -0,0 +1,36 @@ +//@flow +import React from 'react'; +import injectSheet from 'react-jss'; +import classNames from 'classnames'; + +import { Route, withRouter } from 'react-router'; + +import Page from './containers/Page'; +import {Switch} from 'react-router-dom'; + +const styles = { + content: { + paddingTop: '60px' + }, +}; + +type Props = { + classes: any +} + +class Main extends React.Component { + + render() { + const { classes } = this.props; + return ( +
+ + + +
+ ); + } + +} + +export default withRouter(injectSheet(styles)(Main)); diff --git a/scm-ui/src/Navigation.js b/scm-ui/src/Navigation.js new file mode 100644 index 0000000000..76dd76b268 --- /dev/null +++ b/scm-ui/src/Navigation.js @@ -0,0 +1,52 @@ +//@flow +import React from 'react'; +import {Link} from 'react-router-dom'; + +type Props = {}; + +type State = { + collapsed: boolean +}; + +class Navigation extends React.Component { + + constructor(props: Props) { + super(props); + this.state = { + collapsed: true + }; + } + + toggleCollapse = () => { + this.setState({ + collapsed: !this.state.collapsed + }); + }; + + render() { + + return ( + + ); + } + +} + +export default Navigation; diff --git a/scm-ui/src/containers/Page.js b/scm-ui/src/containers/Page.js new file mode 100644 index 0000000000..6594520ff4 --- /dev/null +++ b/scm-ui/src/containers/Page.js @@ -0,0 +1,3 @@ +/** + * Created by masuewer on 02.07.18. + */ diff --git a/scm-ui/src/createReduxStore.js b/scm-ui/src/createReduxStore.js new file mode 100644 index 0000000000..9b53bb0f3d --- /dev/null +++ b/scm-ui/src/createReduxStore.js @@ -0,0 +1,22 @@ +import thunk from 'redux-thunk'; +import logger from 'redux-logger'; +import { createStore, compose, applyMiddleware, combineReducers } from 'redux'; +import { routerReducer, routerMiddleware } from 'react-router-redux'; + +import page from './modules/page'; + +function createReduxStore(history) { + const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; + + const reducer = combineReducers({ + router: routerReducer, + page + }); + + return createStore( + reducer, + composeEnhancers(applyMiddleware(routerMiddleware(history), thunk, logger)) + ); +} + +export default createReduxStore; diff --git a/scm-ui/src/index.js b/scm-ui/src/index.js new file mode 100644 index 0000000000..12d8f58f94 --- /dev/null +++ b/scm-ui/src/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; +import registerServiceWorker from './registerServiceWorker'; + +import { Provider } from 'react-redux'; +import createHistory from 'history/createBrowserHistory'; +import createReduxStore from './createReduxStore'; +import { ConnectedRouter } from 'react-router-redux'; + + +// Create a history of your choosing (we're using a browser history in this case) +const history = createHistory({ + basename: process.env.PUBLIC_URL +}); + +window.appHistory = history; +// Add the reducer to your store on the `router` key +// Also apply our middleware for navigating +const store = createReduxStore(history); + +ReactDOM.render( + + { /* ConnectedRouter will use the store from Provider automatically */} + + + + , + document.getElementById('root') +); + +registerServiceWorker(); diff --git a/scm-ui/src/modules/page.js b/scm-ui/src/modules/page.js new file mode 100644 index 0000000000..6594520ff4 --- /dev/null +++ b/scm-ui/src/modules/page.js @@ -0,0 +1,3 @@ +/** + * Created by masuewer on 02.07.18. + */ diff --git a/scm-ui/src/registerServiceWorker.js b/scm-ui/src/registerServiceWorker.js new file mode 100644 index 0000000000..a3e6c0cfc1 --- /dev/null +++ b/scm-ui/src/registerServiceWorker.js @@ -0,0 +1,117 @@ +// In production, we register a service worker to serve assets from local cache. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on the "N+1" visit to a page, since previously +// cached resources are updated in the background. + +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. +// This link also includes instructions on opting out of this behavior. + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export default function register() { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Lets check if a service worker still exists or not. + checkValidServiceWorker(swUrl); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://goo.gl/SC7cgQ' + ); + }); + } else { + // Is not local host. Just register service worker + registerValidSW(swUrl); + } + }); + } +} + +function registerValidSW(swUrl) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a "New content is + // available; please refresh." message in your web app. + console.log('New content is available; please refresh.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if ( + response.status === 404 || + response.headers.get('content-type').indexOf('javascript') === -1 + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} diff --git a/scm.iml b/scm.iml new file mode 100644 index 0000000000..a0d516ed9a --- /dev/null +++ b/scm.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file From e087920ba500ded56f075ecba0f7881bf0be9322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 2 Jul 2018 16:05:58 +0200 Subject: [PATCH 005/186] added mockup users page and login page --- scm-ui/src/Login.js | 36 +++++++++++++++++++ scm-ui/src/Main.js | 4 ++- scm-ui/src/containers/Page.js | 66 ++++++++++++++++++++++++++++++++-- scm-ui/src/containers/Users.js | 58 ++++++++++++++++++++++++++++++ scm-ui/src/createReduxStore.js | 4 ++- scm-ui/src/modules/page.js | 64 +++++++++++++++++++++++++++++++-- scm-ui/src/modules/users.js | 61 +++++++++++++++++++++++++++++++ 7 files changed, 285 insertions(+), 8 deletions(-) create mode 100644 scm-ui/src/Login.js create mode 100644 scm-ui/src/containers/Users.js create mode 100644 scm-ui/src/modules/users.js diff --git a/scm-ui/src/Login.js b/scm-ui/src/Login.js new file mode 100644 index 0000000000..d6ef5a29a1 --- /dev/null +++ b/scm-ui/src/Login.js @@ -0,0 +1,36 @@ +//@flow +import React from 'react'; +import injectSheet from 'react-jss'; + +const styles = { + wrapper: { + width: '100%', + display: 'flex', + height: '10em' + }, + loading: { + margin: 'auto', + textAlign: 'center' + } +}; + +type Props = { + classes: any; +} + +class Login extends React.Component { + + render() { + const { classes } = this.props; + return ( +
+
+ You need to log in! ... +
+
+ ); + } + +} + +export default injectSheet(styles)(Login); diff --git a/scm-ui/src/Main.js b/scm-ui/src/Main.js index 068bc38a01..2a6fe40ef3 100644 --- a/scm-ui/src/Main.js +++ b/scm-ui/src/Main.js @@ -6,6 +6,7 @@ import classNames from 'classnames'; import { Route, withRouter } from 'react-router'; import Page from './containers/Page'; +import Users from './containers/Users'; import {Switch} from 'react-router-dom'; const styles = { @@ -25,7 +26,8 @@ class Main extends React.Component { return (
- + +
); diff --git a/scm-ui/src/containers/Page.js b/scm-ui/src/containers/Page.js index 6594520ff4..f94914c2b8 100644 --- a/scm-ui/src/containers/Page.js +++ b/scm-ui/src/containers/Page.js @@ -1,3 +1,63 @@ -/** - * Created by masuewer on 02.07.18. - */ +// @flow +import React from 'react'; +import { connect } from 'react-redux'; + +import { fetchRepositoriesIfNeeded } from '../modules/page'; +import Login from '../Login'; + + +type Props = { + loading: boolean, + error: any, + repositories: any, + fetchRepositoriesIfNeeded: () => void +} + +class Page extends React.Component { + + componentDidMount() { + this.props.fetchRepositoriesIfNeeded(); + } + + render() { + const { loading, error, repositories } = this.props; + + + if(loading) { + return ( +
+

SCM

+ +
+ ); + } + else if(!loading){ + return ( +
+

SCM

+

Startpage

+
+ Users hier! + +
+ ); + } + + + } + +} + +const mapStateToProps = (state) => { + return null; +}; + +const mapDispatchToProps = (dispatch) => { + return { + fetchRepositoriesIfNeeded: () => { + dispatch(fetchRepositoriesIfNeeded()) + } + } +}; + +export default connect(mapStateToProps, mapDispatchToProps)(Page); diff --git a/scm-ui/src/containers/Users.js b/scm-ui/src/containers/Users.js new file mode 100644 index 0000000000..00b22862f8 --- /dev/null +++ b/scm-ui/src/containers/Users.js @@ -0,0 +1,58 @@ +// @flow +import React from 'react'; +import { connect } from 'react-redux'; + +import { fetchRepositoriesIfNeeded } from '../modules/users'; +import Login from '../Login'; + +type Props = { + loading: boolean, + error: any, + repositories: any, + fetchRepositoriesIfNeeded: () => void +} + +class Users extends React.Component { + + componentDidMount() { + this.props.fetchRepositoriesIfNeeded(); + } + + render() { + const { loading, error, repositories } = this.props; + + + + if(loading) { + return ( +
+

SCM

+ +
+ ); + } + else if(!loading){ + return ( +
+

SCM

+

Users

+
+ ); + } + } + +} + +const mapStateToProps = (state) => { + return null; +}; + +const mapDispatchToProps = (dispatch) => { + return { + fetchRepositoriesIfNeeded: () => { + dispatch(fetchRepositoriesIfNeeded()) + } + } +}; + +export default connect(mapStateToProps, mapDispatchToProps)(Users); diff --git a/scm-ui/src/createReduxStore.js b/scm-ui/src/createReduxStore.js index 9b53bb0f3d..244c5699b5 100644 --- a/scm-ui/src/createReduxStore.js +++ b/scm-ui/src/createReduxStore.js @@ -4,13 +4,15 @@ import { createStore, compose, applyMiddleware, combineReducers } from 'redux'; import { routerReducer, routerMiddleware } from 'react-router-redux'; import page from './modules/page'; +import users from './modules/users'; function createReduxStore(history) { const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const reducer = combineReducers({ router: routerReducer, - page + page, + users }); return createStore( diff --git a/scm-ui/src/modules/page.js b/scm-ui/src/modules/page.js index 6594520ff4..fe8db685e0 100644 --- a/scm-ui/src/modules/page.js +++ b/scm-ui/src/modules/page.js @@ -1,3 +1,61 @@ -/** - * Created by masuewer on 02.07.18. - */ +//@flow +const FETCH_REPOSITORIES = 'smeagol/repositories/FETCH'; +const FETCH_REPOSITORIES_SUCCESS = 'smeagol/repositories/FETCH_SUCCESS'; +const FETCH_REPOSITORIES_FAILURE = 'smeagol/repositories/FETCH_FAILURE'; + +const THRESHOLD_TIMESTAMP = 10000; + +function requestRepositories() { + return { + type: FETCH_REPOSITORIES + }; +} + + +function fetchRepositories() { + return function(dispatch) { + dispatch(requestRepositories()); + return null; + } +} + +export function shouldFetchRepositories(state: any): boolean { + const repositories = state.repositories; + return null; +} + +export function fetchRepositoriesIfNeeded() { + return (dispatch, getState) => { + if (shouldFetchRepositories(getState())) { + dispatch(fetchRepositories()); + } + } +} + +export default function reducer(state = {}, action = {}) { + switch (action.type) { + case FETCH_REPOSITORIES: + return { + ...state, + loading: true, + error: null + }; + case FETCH_REPOSITORIES_SUCCESS: + return { + ...state, + loading: true, + timestamp: action.timestamp, + error: null, + repositories: action.payload + }; + case FETCH_REPOSITORIES_FAILURE: + return { + ...state, + loading: true, + error: action.payload + }; + + default: + return state + } +} diff --git a/scm-ui/src/modules/users.js b/scm-ui/src/modules/users.js new file mode 100644 index 0000000000..b482010b61 --- /dev/null +++ b/scm-ui/src/modules/users.js @@ -0,0 +1,61 @@ +//@flow +const FETCH_REPOSITORIES = 'smeagol/repositories/FETCH'; +const FETCH_REPOSITORIES_SUCCESS = 'smeagol/repositories/FETCH_SUCCESS'; +const FETCH_REPOSITORIES_FAILURE = 'smeagol/repositories/FETCH_FAILURE'; + +const THRESHOLD_TIMESTAMP = 10000; + +function requestRepositories() { + return { + type: FETCH_REPOSITORIES + }; +} + + +function fetchRepositories() { + return function(dispatch) { + dispatch(requestRepositories()); + return null; + } +} + +export function shouldFetchRepositories(state: any): boolean { + const repositories = state.repositories; + return null; +} + +export function fetchRepositoriesIfNeeded() { + return (dispatch, getState) => { + if (shouldFetchRepositories(getState())) { + dispatch(fetchRepositories()); + } + } +} + +export default function reducer(state = {}, action = {}) { + switch (action.type) { + case FETCH_REPOSITORIES: + return { + ...state, + loading: true, + error: null + }; + case FETCH_REPOSITORIES_SUCCESS: + return { + ...state, + loading: false, + timestamp: action.timestamp, + error: null, + repositories: action.payload + }; + case FETCH_REPOSITORIES_FAILURE: + return { + ...state, + loading: false, + error: action.payload + }; + + default: + return state + } +} From 69a081ccf8e6a574443089c15444149355d1adaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Mon, 2 Jul 2018 16:22:24 +0200 Subject: [PATCH 006/186] renaming: loading to login --- scm-ui/src/Login.js | 4 ++-- scm-ui/src/containers/Page.js | 8 ++++---- scm-ui/src/containers/Users.js | 8 ++++---- scm-ui/src/modules/page.js | 6 +++--- scm-ui/src/modules/users.js | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/scm-ui/src/Login.js b/scm-ui/src/Login.js index d6ef5a29a1..f85fbef9ed 100644 --- a/scm-ui/src/Login.js +++ b/scm-ui/src/Login.js @@ -8,7 +8,7 @@ const styles = { display: 'flex', height: '10em' }, - loading: { + login: { margin: 'auto', textAlign: 'center' } @@ -24,7 +24,7 @@ class Login extends React.Component { const { classes } = this.props; return (
-
+
You need to log in! ...
diff --git a/scm-ui/src/containers/Page.js b/scm-ui/src/containers/Page.js index f94914c2b8..7586be9167 100644 --- a/scm-ui/src/containers/Page.js +++ b/scm-ui/src/containers/Page.js @@ -7,7 +7,7 @@ import Login from '../Login'; type Props = { - loading: boolean, + login: boolean, error: any, repositories: any, fetchRepositoriesIfNeeded: () => void @@ -20,10 +20,10 @@ class Page extends React.Component { } render() { - const { loading, error, repositories } = this.props; + const { login, error, repositories } = this.props; - if(loading) { + if(login) { return (

SCM

@@ -31,7 +31,7 @@ class Page extends React.Component {
); } - else if(!loading){ + else if(!login){ return (

SCM

diff --git a/scm-ui/src/containers/Users.js b/scm-ui/src/containers/Users.js index 00b22862f8..8a4bfd5494 100644 --- a/scm-ui/src/containers/Users.js +++ b/scm-ui/src/containers/Users.js @@ -6,7 +6,7 @@ import { fetchRepositoriesIfNeeded } from '../modules/users'; import Login from '../Login'; type Props = { - loading: boolean, + login: boolean, error: any, repositories: any, fetchRepositoriesIfNeeded: () => void @@ -19,11 +19,11 @@ class Users extends React.Component { } render() { - const { loading, error, repositories } = this.props; + const { login, error, repositories } = this.props; - if(loading) { + if(login) { return (

SCM

@@ -31,7 +31,7 @@ class Users extends React.Component {
); } - else if(!loading){ + else if(!login){ return (

SCM

diff --git a/scm-ui/src/modules/page.js b/scm-ui/src/modules/page.js index fe8db685e0..58fd12ca18 100644 --- a/scm-ui/src/modules/page.js +++ b/scm-ui/src/modules/page.js @@ -37,13 +37,13 @@ export default function reducer(state = {}, action = {}) { case FETCH_REPOSITORIES: return { ...state, - loading: true, + login: true, error: null }; case FETCH_REPOSITORIES_SUCCESS: return { ...state, - loading: true, + login: true, timestamp: action.timestamp, error: null, repositories: action.payload @@ -51,7 +51,7 @@ export default function reducer(state = {}, action = {}) { case FETCH_REPOSITORIES_FAILURE: return { ...state, - loading: true, + login: true, error: action.payload }; diff --git a/scm-ui/src/modules/users.js b/scm-ui/src/modules/users.js index b482010b61..d4617c3a47 100644 --- a/scm-ui/src/modules/users.js +++ b/scm-ui/src/modules/users.js @@ -37,13 +37,13 @@ export default function reducer(state = {}, action = {}) { case FETCH_REPOSITORIES: return { ...state, - loading: true, + login: true, error: null }; case FETCH_REPOSITORIES_SUCCESS: return { ...state, - loading: false, + login: false, timestamp: action.timestamp, error: null, repositories: action.payload @@ -51,7 +51,7 @@ export default function reducer(state = {}, action = {}) { case FETCH_REPOSITORIES_FAILURE: return { ...state, - loading: false, + login: false, error: action.payload }; From 85902010ce1c4801005bd6d60b35d87e3bacc335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Wed, 4 Jul 2018 09:55:02 +0200 Subject: [PATCH 007/186] refactoring/renaming --- scm-ui/src/Main.js | 4 +-- .../containers/{Page.js => Repositories.js} | 8 ++--- scm-ui/src/containers/Users.js | 14 ++++---- scm-ui/src/createReduxStore.js | 4 +-- .../src/modules/{page.js => repositories.js} | 8 ++--- scm-ui/src/modules/users.js | 32 +++++++++---------- 6 files changed, 33 insertions(+), 37 deletions(-) rename scm-ui/src/containers/{Page.js => Repositories.js} (78%) rename scm-ui/src/modules/{page.js => repositories.js} (81%) diff --git a/scm-ui/src/Main.js b/scm-ui/src/Main.js index 2a6fe40ef3..10e7d4f0c2 100644 --- a/scm-ui/src/Main.js +++ b/scm-ui/src/Main.js @@ -5,7 +5,7 @@ import classNames from 'classnames'; import { Route, withRouter } from 'react-router'; -import Page from './containers/Page'; +import Repositories from './containers/Repositories'; import Users from './containers/Users'; import {Switch} from 'react-router-dom'; @@ -26,7 +26,7 @@ class Main extends React.Component { return (
- +
diff --git a/scm-ui/src/containers/Page.js b/scm-ui/src/containers/Repositories.js similarity index 78% rename from scm-ui/src/containers/Page.js rename to scm-ui/src/containers/Repositories.js index 7586be9167..cb4aca4373 100644 --- a/scm-ui/src/containers/Page.js +++ b/scm-ui/src/containers/Repositories.js @@ -2,18 +2,16 @@ import React from 'react'; import { connect } from 'react-redux'; -import { fetchRepositoriesIfNeeded } from '../modules/page'; +import { fetchRepositoriesIfNeeded } from '../modules/repositories'; import Login from '../Login'; type Props = { login: boolean, error: any, - repositories: any, - fetchRepositoriesIfNeeded: () => void } -class Page extends React.Component { +class Repositories extends React.Component { componentDidMount() { this.props.fetchRepositoriesIfNeeded(); @@ -60,4 +58,4 @@ const mapDispatchToProps = (dispatch) => { } }; -export default connect(mapStateToProps, mapDispatchToProps)(Page); +export default connect(mapStateToProps, mapDispatchToProps)(Repositories); diff --git a/scm-ui/src/containers/Users.js b/scm-ui/src/containers/Users.js index 8a4bfd5494..1e4b0bfba8 100644 --- a/scm-ui/src/containers/Users.js +++ b/scm-ui/src/containers/Users.js @@ -2,24 +2,24 @@ import React from 'react'; import { connect } from 'react-redux'; -import { fetchRepositoriesIfNeeded } from '../modules/users'; +import { fetchUsersIfNeeded } from '../modules/users'; import Login from '../Login'; type Props = { login: boolean, error: any, - repositories: any, - fetchRepositoriesIfNeeded: () => void + users: any, + fetchUsersIfNeeded: () => void } class Users extends React.Component { componentDidMount() { - this.props.fetchRepositoriesIfNeeded(); + this.props.fetchUsersIfNeeded(); } render() { - const { login, error, repositories } = this.props; + const { login, error, users } = this.props; @@ -49,8 +49,8 @@ const mapStateToProps = (state) => { const mapDispatchToProps = (dispatch) => { return { - fetchRepositoriesIfNeeded: () => { - dispatch(fetchRepositoriesIfNeeded()) + fetchUsersIfNeeded: () => { + dispatch(fetchUsersIfNeeded()) } } }; diff --git a/scm-ui/src/createReduxStore.js b/scm-ui/src/createReduxStore.js index 244c5699b5..5077df3e54 100644 --- a/scm-ui/src/createReduxStore.js +++ b/scm-ui/src/createReduxStore.js @@ -3,7 +3,7 @@ import logger from 'redux-logger'; import { createStore, compose, applyMiddleware, combineReducers } from 'redux'; import { routerReducer, routerMiddleware } from 'react-router-redux'; -import page from './modules/page'; +import repositories from './modules/repositories'; import users from './modules/users'; function createReduxStore(history) { @@ -11,7 +11,7 @@ function createReduxStore(history) { const reducer = combineReducers({ router: routerReducer, - page, + repositories, users }); diff --git a/scm-ui/src/modules/page.js b/scm-ui/src/modules/repositories.js similarity index 81% rename from scm-ui/src/modules/page.js rename to scm-ui/src/modules/repositories.js index 58fd12ca18..c58f613330 100644 --- a/scm-ui/src/modules/page.js +++ b/scm-ui/src/modules/repositories.js @@ -1,9 +1,7 @@ //@flow -const FETCH_REPOSITORIES = 'smeagol/repositories/FETCH'; -const FETCH_REPOSITORIES_SUCCESS = 'smeagol/repositories/FETCH_SUCCESS'; -const FETCH_REPOSITORIES_FAILURE = 'smeagol/repositories/FETCH_FAILURE'; - -const THRESHOLD_TIMESTAMP = 10000; +const FETCH_REPOSITORIES = 'scm/repositories/FETCH'; +const FETCH_REPOSITORIES_SUCCESS = 'scm/repositories/FETCH_SUCCESS'; +const FETCH_REPOSITORIES_FAILURE = 'scm/repositories/FETCH_FAILURE'; function requestRepositories() { return { diff --git a/scm-ui/src/modules/users.js b/scm-ui/src/modules/users.js index d4617c3a47..2afdfdfd8a 100644 --- a/scm-ui/src/modules/users.js +++ b/scm-ui/src/modules/users.js @@ -1,54 +1,54 @@ //@flow -const FETCH_REPOSITORIES = 'smeagol/repositories/FETCH'; -const FETCH_REPOSITORIES_SUCCESS = 'smeagol/repositories/FETCH_SUCCESS'; -const FETCH_REPOSITORIES_FAILURE = 'smeagol/repositories/FETCH_FAILURE'; +const FETCH_USERS = 'scm/users/FETCH'; +const FETCH_USERS_SUCCESS= 'scm/users/FETCH_SUCCESS'; +const FETCH_USERS_FAILURE = 'scm/users/FETCH_FAILURE'; const THRESHOLD_TIMESTAMP = 10000; -function requestRepositories() { +function requestUsers() { return { - type: FETCH_REPOSITORIES + type: FETCH_USERS }; } -function fetchRepositories() { +function fetchUsers() { return function(dispatch) { - dispatch(requestRepositories()); + dispatch(requestUsers()); return null; } } -export function shouldFetchRepositories(state: any): boolean { - const repositories = state.repositories; +export function shouldFetchUsers(state: any): boolean { + const users = state.users; return null; } -export function fetchRepositoriesIfNeeded() { +export function fetchUsersIfNeeded() { return (dispatch, getState) => { - if (shouldFetchRepositories(getState())) { - dispatch(fetchRepositories()); + if (shouldFetchUsers(getState())) { + dispatch(fetchUsers()); } } } export default function reducer(state = {}, action = {}) { switch (action.type) { - case FETCH_REPOSITORIES: + case FETCH_USERS: return { ...state, login: true, error: null }; - case FETCH_REPOSITORIES_SUCCESS: + case FETCH_USERS_SUCCESS: return { ...state, login: false, timestamp: action.timestamp, error: null, - repositories: action.payload + users: action.payload }; - case FETCH_REPOSITORIES_FAILURE: + case FETCH_USERS_FAILURE: return { ...state, login: false, From d3a2fc8219e8a6217a39e1b52c80fc005ae0ad84 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 4 Jul 2018 15:46:08 +0200 Subject: [PATCH 008/186] Implemented namespace feature --- .../sonia/scm/config/ScmConfiguration.java | 600 ++++++------------ .../repository/NamespaceStrategyProvider.java | 32 + .../java/sonia/scm/repository/Repository.java | 31 +- .../main/java/sonia/scm/ScmServletModule.java | 45 +- .../repository/DefaultNamespaceStrategy.java | 11 +- .../repository/DefaultRepositoryManager.java | 209 +----- .../DefaultRepositoryManagerPerfTest.java | 22 +- .../DefaultRepositoryManagerTest.java | 42 +- 8 files changed, 262 insertions(+), 730 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/NamespaceStrategyProvider.java diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index ac22c1403e..c7c3517099 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -1,19 +1,19 @@ /** * 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. + * 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. + * 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. - * + * 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 @@ -24,39 +24,32 @@ * 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.config; -//~--- non-JDK imports -------------------------------------------------------- import com.google.common.collect.Sets; import com.google.inject.Singleton; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.event.ScmEventBus; import sonia.scm.util.HttpUtil; import sonia.scm.xml.XmlSetStringAdapter; -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; - -import java.util.Set; -import java.util.concurrent.TimeUnit; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.io.File; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +//~--- JDK imports ------------------------------------------------------------ /** * The main configuration object for SCM-Manager. @@ -67,38 +60,137 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @Singleton @XmlRootElement(name = "scm-config") @XmlAccessorType(XmlAccessType.FIELD) -public class ScmConfiguration -{ +public class ScmConfiguration { - /** Default JavaScript date format */ + /** + * Default JavaScript date format + */ public static final String DEFAULT_DATEFORMAT = "YYYY-MM-DD HH:mm:ss"; - /** Default plugin url */ + /** + * Default plugin url + */ public static final String DEFAULT_PLUGINURL = "http://plugins.scm-manager.org/scm-plugin-backend/api/{version}/plugins?os={os}&arch={arch}&snapshot=false"; - /** Default plugin url from version 1.0 */ + /** + * Default plugin url from version 1.0 + */ public static final String OLD_PLUGINURL = "http://plugins.scm-manager.org/plugins.xml.gz"; - /** Path to the configuration file */ + /** + * Path to the configuration file + */ public static final String PATH = "config".concat(File.separator).concat("config.xml"); - /** the logger for ScmConfiguration */ + /** + * the logger for ScmConfiguration + */ private static final Logger logger = LoggerFactory.getLogger(ScmConfiguration.class); - //~--- methods -------------------------------------------------------------- + + @XmlElement(name = "admin-groups") + @XmlJavaTypeAdapter(XmlSetStringAdapter.class) + private Set adminGroups; + + + @XmlElement(name = "admin-users") + @XmlJavaTypeAdapter(XmlSetStringAdapter.class) + private Set adminUsers; + + + @XmlElement(name = "base-url") + private String baseUrl; + + + @XmlElement(name = "force-base-url") + private boolean forceBaseUrl; + + /** + * Maximum allowed login attempts. + * + * @since 1.34 + */ + @XmlElement(name = "login-attempt-limit") + private int loginAttemptLimit = -1; + + /** + * glob patterns for urls which are excluded from proxy + */ + @XmlElement(name = "proxy-excludes") + @XmlJavaTypeAdapter(XmlSetStringAdapter.class) + private Set proxyExcludes; + + + private String proxyPassword; + + + private int proxyPort = 8080; + + + private String proxyServer = "proxy.mydomain.com"; + + + private String proxyUser; + + /** + * Skip failed authenticators. + * + * @since 1.36 + */ + @XmlElement(name = "skip-failed-authenticators") + private boolean skipFailedAuthenticators = false; + + + @XmlElement(name = "plugin-url") + private String pluginUrl = DEFAULT_PLUGINURL; + + /** + * Login attempt timeout. + * + * @since 1.34 + */ + @XmlElement(name = "login-attempt-limit-timeout") + private long loginAttemptLimitTimeout = TimeUnit.MINUTES.toSeconds(5l); + + + private boolean enableProxy = false; + + /** + * Authentication realm for basic authentication. + */ + private String realmDescription = HttpUtil.AUTHENTICATION_REALM; + private boolean enableRepositoryArchive = false; + private boolean disableGroupingGrid = false; + /** + * JavaScript date format from moment.js + * + * @see http://momentjs.com/docs/#/parsing/ + */ + private String dateFormat = DEFAULT_DATEFORMAT; + private boolean anonymousAccessEnabled = false; + + /** + * Enables xsrf cookie protection. + * + * @since 1.47 + */ + @XmlElement(name = "xsrf-protection") + private boolean enabledXsrfProtection = true; + + @XmlElement(name = "default-namespace-strategy") + private String defaultNamespaceStrategy = "sonia.scm.repository.DefaultNamespaceStrategy"; + /** * Calls the {@link sonia.scm.ConfigChangedListener#configChanged(Object)} * method of all registered listeners. */ - public void fireChangeEvent() - { - if (logger.isDebugEnabled()) - { + public void fireChangeEvent() { + if (logger.isDebugEnabled()) { logger.debug("fire config changed event"); } @@ -109,12 +201,9 @@ public class ScmConfiguration /** * Load all properties from another {@link ScmConfiguration} object. * - * - * * @param other */ - public void load(ScmConfiguration other) - { + public void load(ScmConfiguration other) { this.realmDescription = other.realmDescription; this.dateFormat = other.dateFormat; this.pluginUrl = other.pluginUrl; @@ -135,29 +224,14 @@ public class ScmConfiguration this.loginAttemptLimit = other.loginAttemptLimit; this.loginAttemptLimitTimeout = other.loginAttemptLimitTimeout; this.enabledXsrfProtection = other.enabledXsrfProtection; + this.defaultNamespaceStrategy = other.defaultNamespaceStrategy; } - //~--- get methods ---------------------------------------------------------- - - /** - * Returns a set of admin group names. - * - * - * @return set of admin group names - */ - public Set getAdminGroups() - { + public Set getAdminGroups() { return adminGroups; } - /** - * Returns a set of admin user names. - * - * - * @return set of admin user names - */ - public Set getAdminUsers() - { + public Set getAdminUsers() { return adminUsers; } @@ -165,11 +239,10 @@ public class ScmConfiguration * Returns the complete base url of the scm-manager including the context path. * For example http://localhost:8080/scm * - * @since 1.5 * @return complete base url of the scm-manager + * @since 1.5 */ - public String getBaseUrl() - { + public String getBaseUrl() { return baseUrl; } @@ -177,23 +250,14 @@ public class ScmConfiguration * Returns the date format for the user interface. This format is a * JavaScript date format, from the library moment.js. * - * @see http://momentjs.com/docs/#/parsing/ * @return moment.js date format + * @see http://momentjs.com/docs/#/parsing/ */ - public String getDateFormat() - { + public String getDateFormat() { return dateFormat; } - /** - * Returns maximum allowed login attempts. - * - * @return maximum allowed login attempts - * - * @since 1.34 - */ - public int getLoginAttemptLimit() - { + public int getLoginAttemptLimit() { return loginAttemptLimit; } @@ -202,11 +266,9 @@ public class ScmConfiguration * because of too many failed login attempts. * * @return login attempt timeout in seconds - * * @since 1.34 */ - public long getLoginAttemptLimitTimeout() - { + public long getLoginAttemptLimitTimeout() { return loginAttemptLimitTimeout; } @@ -222,8 +284,7 @@ public class ScmConfiguration * * @return the complete plugin url. */ - public String getPluginUrl() - { + public String getPluginUrl() { return pluginUrl; } @@ -231,289 +292,141 @@ public class ScmConfiguration * Returns a set of glob patterns for urls which should excluded from * proxy settings. * - * * @return set of glob patterns * @since 1.23 */ - public Set getProxyExcludes() - { - if (proxyExcludes == null) - { + public Set getProxyExcludes() { + if (proxyExcludes == null) { proxyExcludes = Sets.newHashSet(); } return proxyExcludes; } - /** - * Method description - * - * - * @return - * @since 1.7 - */ - public String getProxyPassword() - { + public String getProxyPassword() { return proxyPassword; } - /** - * Returns the proxy port. - * - * - * @return proxy port - */ - public int getProxyPort() - { + public int getProxyPort() { return proxyPort; } /** * Returns the servername or ip of the proxyserver. * - * * @return servername or ip of the proxyserver */ - public String getProxyServer() - { + public String getProxyServer() { return proxyServer; } - /** - * Method description - * - * - * @return - * @since 1.7 - */ - public String getProxyUser() - { + public String getProxyUser() { return proxyUser; } - /** - * Returns the realm description. - * - * - * @return realm description - * @since 1.36 - */ - public String getRealmDescription() - { + public String getRealmDescription() { return realmDescription; } - - /** - * Returns true if the anonymous access to the SCM-Manager is enabled. - * - * - * @return true if the anonymous access to the SCM-Manager is enabled - */ - public boolean isAnonymousAccessEnabled() - { + public boolean isAnonymousAccessEnabled() { return anonymousAccessEnabled; } - /** - * Method description - * - * @since 1.9 - * @return - */ - public boolean isDisableGroupingGrid() - { + public boolean isDisableGroupingGrid() { return disableGroupingGrid; } /** * Returns {@code true} if the cookie xsrf protection is enabled. - * - * @see Issue 793 + * * @return {@code true} if the cookie xsrf protection is enabled - * + * @see Issue 793 * @since 1.47 */ - public boolean isEnabledXsrfProtection() - { + public boolean isEnabledXsrfProtection() { return enabledXsrfProtection; } - /** - * Returns true if proxy is enabled. - * - * - * @return true if proxy is enabled - */ - public boolean isEnableProxy() - { + public boolean isEnableProxy() { return enableProxy; } - /** - * Returns true if the repository archive is enabled. - * - * - * @return true if the repository archive is enabled - * @since 1.14 - */ - public boolean isEnableRepositoryArchive() - { + public boolean isEnableRepositoryArchive() { return enableRepositoryArchive; } - /** - * Returns true if force base url is enabled. - * - * @since 1.5 - * @return true if force base url is enabled - */ - public boolean isForceBaseUrl() - { + public boolean isForceBaseUrl() { return forceBaseUrl; } - /** - * Returns true if the login attempt limit is enabled. - * - * - * @return true if login attempt limit is enabled - * - * @since 1.37 - */ - public boolean isLoginAttemptLimitEnabled() - { + public boolean isLoginAttemptLimitEnabled() { return loginAttemptLimit > 0; } + public String getDefaultNamespaceStrategy() { + return defaultNamespaceStrategy; + } + + /** * Returns true if failed authenticators are skipped. * - * * @return true if failed authenticators are skipped - * * @since 1.36 */ - public boolean isSkipFailedAuthenticators() - { + public boolean isSkipFailedAuthenticators() { return skipFailedAuthenticators; } - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param adminGroups - */ - public void setAdminGroups(Set adminGroups) - { + public void setAdminGroups(Set adminGroups) { this.adminGroups = adminGroups; } - /** - * Method description - * - * - * @param adminUsers - */ - public void setAdminUsers(Set adminUsers) - { + public void setAdminUsers(Set adminUsers) { this.adminUsers = adminUsers; } - /** - * Method description - * - * - * @param anonymousAccessEnabled - */ - public void setAnonymousAccessEnabled(boolean anonymousAccessEnabled) - { + public void setAnonymousAccessEnabled(boolean anonymousAccessEnabled) { this.anonymousAccessEnabled = anonymousAccessEnabled; } - /** - * Method description - * - * - * @param baseUrl - * @since 1.5 - */ - public void setBaseUrl(String baseUrl) - { + public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } - /** - * Sets the date format for the ui. - * - * - * @param dateFormat date format for ui - */ - public void setDateFormat(String dateFormat) - { + public void setDateFormat(String dateFormat) { this.dateFormat = dateFormat; } - /** - * Method description - * - * @since 1.9 - * - * @param disableGroupingGrid - */ - public void setDisableGroupingGrid(boolean disableGroupingGrid) - { + public void setDisableGroupingGrid(boolean disableGroupingGrid) { this.disableGroupingGrid = disableGroupingGrid; } - /** - * Method description - * - * - * @param enableProxy - */ - public void setEnableProxy(boolean enableProxy) - { + public void setEnableProxy(boolean enableProxy) { this.enableProxy = enableProxy; } /** * Enable or disable the repository archive. Default is disabled. * - * * @param enableRepositoryArchive true to disable the repository archive * @since 1.14 */ - public void setEnableRepositoryArchive(boolean enableRepositoryArchive) - { + public void setEnableRepositoryArchive(boolean enableRepositoryArchive) { this.enableRepositoryArchive = enableRepositoryArchive; } - /** - * Method description - * - * - * @param forceBaseUrl - * @since 1.5 - */ - public void setForceBaseUrl(boolean forceBaseUrl) - { + public void setForceBaseUrl(boolean forceBaseUrl) { this.forceBaseUrl = forceBaseUrl; } /** * Set maximum allowed login attempts. * - * * @param loginAttemptLimit login attempt limit - * * @since 1.34 */ - public void setLoginAttemptLimit(int loginAttemptLimit) - { + public void setLoginAttemptLimit(int loginAttemptLimit) { this.loginAttemptLimit = loginAttemptLimit; } @@ -522,22 +435,13 @@ public class ScmConfiguration * because of too many failed login attempts. * * @param loginAttemptLimitTimeout login attempt timeout in seconds - * * @since 1.34 */ - public void setLoginAttemptLimitTimeout(long loginAttemptLimitTimeout) - { + public void setLoginAttemptLimitTimeout(long loginAttemptLimitTimeout) { this.loginAttemptLimitTimeout = loginAttemptLimitTimeout; } - /** - * Method description - * - * - * @param pluginUrl - */ - public void setPluginUrl(String pluginUrl) - { + public void setPluginUrl(String pluginUrl) { this.pluginUrl = pluginUrl; } @@ -545,194 +449,56 @@ public class ScmConfiguration * Set glob patterns for urls which are should be excluded from proxy * settings. * - * * @param proxyExcludes glob patterns * @since 1.23 */ - public void setProxyExcludes(Set proxyExcludes) - { + public void setProxyExcludes(Set proxyExcludes) { this.proxyExcludes = proxyExcludes; } - /** - * Method description - * - * - * @param proxyPassword - * @since 1.7 - */ - public void setProxyPassword(String proxyPassword) - { + public void setProxyPassword(String proxyPassword) { this.proxyPassword = proxyPassword; } - /** - * Method description - * - * - * @param proxyPort - */ - public void setProxyPort(int proxyPort) - { + public void setProxyPort(int proxyPort) { this.proxyPort = proxyPort; } - /** - * Method description - * - * - * @param proxyServer - */ - public void setProxyServer(String proxyServer) - { + public void setProxyServer(String proxyServer) { this.proxyServer = proxyServer; } - /** - * Method description - * - * - * @param proxyUser - * @since 1.7 - */ - public void setProxyUser(String proxyUser) - { + public void setProxyUser(String proxyUser) { this.proxyUser = proxyUser; } - /** - * Sets the realm description. - * - * - * @param realmDescription - * @since 1.36 - */ - public void setRealmDescription(String realmDescription) - { + public void setRealmDescription(String realmDescription) { this.realmDescription = realmDescription; } /** - * If set to true the authentication chain is not stopped, if an + * If set to true the authentication chain is not stopped, if an * authenticator finds the user but fails to authenticate the user. * * @param skipFailedAuthenticators true to skip failed authenticators - * * @since 1.36 */ - public void setSkipFailedAuthenticators(boolean skipFailedAuthenticators) - { + public void setSkipFailedAuthenticators(boolean skipFailedAuthenticators) { this.skipFailedAuthenticators = skipFailedAuthenticators; } /** * Set {@code true} to enable xsrf cookie protection. - * + * * @param enabledXsrfProtection {@code true} to enable xsrf protection * @see Issue 793 - * * @since 1.47 */ - public void setEnabledXsrfProtection(boolean enabledXsrfProtection) - { + public void setEnabledXsrfProtection(boolean enabledXsrfProtection) { this.enabledXsrfProtection = enabledXsrfProtection; } - //~--- fields --------------------------------------------------------------- - - /** Field description */ - @XmlElement(name = "admin-groups") - @XmlJavaTypeAdapter(XmlSetStringAdapter.class) - private Set adminGroups; - - /** Field description */ - @XmlElement(name = "admin-users") - @XmlJavaTypeAdapter(XmlSetStringAdapter.class) - private Set adminUsers; - - /** Field description */ - @XmlElement(name = "base-url") - private String baseUrl; - - /** Field description */ - @XmlElement(name = "force-base-url") - private boolean forceBaseUrl; - - /** - * Maximum allowed login attempts. - * - * @since 1.34 - */ - @XmlElement(name = "login-attempt-limit") - private int loginAttemptLimit = -1; - - /** glob patterns for urls which are excluded from proxy */ - @XmlElement(name = "proxy-excludes") - @XmlJavaTypeAdapter(XmlSetStringAdapter.class) - private Set proxyExcludes; - - /** Field description */ - private String proxyPassword; - - /** Field description */ - private int proxyPort = 8080; - - /** Field description */ - private String proxyServer = "proxy.mydomain.com"; - - /** Field description */ - private String proxyUser; - - /** - * Skip failed authenticators. - * - * @since 1.36 - */ - @XmlElement(name = "skip-failed-authenticators") - private boolean skipFailedAuthenticators = false; - - /** Field description */ - @XmlElement(name = "plugin-url") - private String pluginUrl = DEFAULT_PLUGINURL; - - /** - * Login attempt timeout. - * - * @since 1.34 - */ - @XmlElement(name = "login-attempt-limit-timeout") - private long loginAttemptLimitTimeout = TimeUnit.MINUTES.toSeconds(5l); - - /** Field description */ - private boolean enableProxy = false; - - /** - * - * Authentication realm for basic authentication. - * - */ - private String realmDescription = HttpUtil.AUTHENTICATION_REALM; - - /** Field description */ - private boolean enableRepositoryArchive = false; - - /** Field description */ - private boolean disableGroupingGrid = false; - - /** - * JavaScript date format from moment.js - * @see http://momentjs.com/docs/#/parsing/ - */ - private String dateFormat = DEFAULT_DATEFORMAT; - - /** Field description */ - private boolean anonymousAccessEnabled = false; - - /** - * Enables xsrf cookie protection. - * - * @since 1.47 - */ - @XmlElement(name = "xsrf-protection") - private boolean enabledXsrfProtection = true; + public void setDefaultNamespaceStrategy(String defaultNamespaceStrategy) { + this.defaultNamespaceStrategy = defaultNamespaceStrategy; + } } diff --git a/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategyProvider.java b/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategyProvider.java new file mode 100644 index 0000000000..49aefe71ac --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/NamespaceStrategyProvider.java @@ -0,0 +1,32 @@ +package sonia.scm.repository; + +import sonia.scm.config.ScmConfiguration; + +import javax.inject.Inject; +import javax.inject.Provider; +import java.util.Set; + +public class NamespaceStrategyProvider implements Provider { + + private final Set strategies; + private final ScmConfiguration scmConfiguration; + + @Inject + public NamespaceStrategyProvider(Set strategies, ScmConfiguration scmConfiguration) { + this.strategies = strategies; + this.scmConfiguration = scmConfiguration; + } + + @Override + public NamespaceStrategy get() { + String namespaceStrategy = scmConfiguration.getDefaultNamespaceStrategy(); + + for (NamespaceStrategy s : this.strategies) { + if (s.getClass().getCanonicalName().equals(namespaceStrategy)) { + return s; + } + } + return null; + } + +} diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 63bef4ea6c..df340564f6 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -37,23 +37,17 @@ import com.github.sdorra.ssp.PermissionObject; import com.github.sdorra.ssp.StaticPermissions; import com.google.common.base.Objects; import com.google.common.collect.Lists; - import sonia.scm.BasicPropertiesAware; import sonia.scm.ModelObject; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; import sonia.scm.util.ValidationUtil; +import javax.xml.bind.annotation.*; import java.util.Arrays; import java.util.Collections; import java.util.List; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElementWrapper; -import javax.xml.bind.annotation.XmlRootElement; - /** * Source code repository. * @@ -178,40 +172,23 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per return healthCheckFailures; } - /** - * Returns the unique id of the {@link Repository}. - * - * @return unique id - */ @Override public String getId() { return id; } - /** - * Returns the timestamp of the last modified date of the {@link Repository}. - * - * @return timestamp of the last modified date - */ @Override public Long getLastModified() { return lastModified; } - /** - * Returns the name of the {@link Repository}. - * - * @return name of the {@link Repository} - */ + public String getName() { return name; } - /** - * Returns the access permissions of the {@link Repository}. - * - * @return access permissions - */ + public String getNamespace() { return namespace; } + public List getPermissions() { if (permissions == null) { permissions = Lists.newArrayList(); diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 0938b8a02a..dc62c54d59 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -57,16 +57,8 @@ import sonia.scm.group.xml.XmlGroupDAO; import sonia.scm.io.DefaultFileSystem; import sonia.scm.io.FileSystem; import sonia.scm.net.SSLContextProvider; -import sonia.scm.net.ahc.AdvancedHttpClient; -import sonia.scm.net.ahc.ContentTransformer; -import sonia.scm.net.ahc.DefaultAdvancedHttpClient; -import sonia.scm.net.ahc.JsonContentTransformer; -import sonia.scm.net.ahc.XmlContentTransformer; -import sonia.scm.plugin.DefaultPluginLoader; -import sonia.scm.plugin.DefaultPluginManager; -import sonia.scm.plugin.ExtensionProcessor; -import sonia.scm.plugin.PluginLoader; -import sonia.scm.plugin.PluginManager; +import sonia.scm.net.ahc.*; +import sonia.scm.plugin.*; import sonia.scm.repository.*; import sonia.scm.repository.api.HookContextFactory; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -78,32 +70,13 @@ import sonia.scm.resources.ResourceManager; import sonia.scm.resources.ScriptResourceServlet; import sonia.scm.schedule.QuartzScheduler; import sonia.scm.schedule.Scheduler; -import sonia.scm.security.AuthorizationChangedEventProducer; -import sonia.scm.security.CipherHandler; -import sonia.scm.security.CipherUtil; -import sonia.scm.security.ConfigurableLoginAttemptHandler; -import sonia.scm.security.DefaultKeyGenerator; -import sonia.scm.security.DefaultSecuritySystem; -import sonia.scm.security.KeyGenerator; -import sonia.scm.security.LoginAttemptHandler; -import sonia.scm.security.SecuritySystem; -import sonia.scm.store.BlobStoreFactory; -import sonia.scm.store.ConfigurationEntryStoreFactory; -import sonia.scm.store.ConfigurationStoreFactory; -import sonia.scm.store.DataStoreFactory; -import sonia.scm.store.FileBlobStoreFactory; -import sonia.scm.store.JAXBConfigurationEntryStoreFactory; -import sonia.scm.store.JAXBConfigurationStoreFactory; -import sonia.scm.store.JAXBDataStoreFactory; +import sonia.scm.security.*; +import sonia.scm.store.*; import sonia.scm.template.MustacheTemplateEngine; import sonia.scm.template.TemplateEngine; import sonia.scm.template.TemplateEngineFactory; import sonia.scm.template.TemplateServlet; -import sonia.scm.url.RestJsonUrlProvider; -import sonia.scm.url.RestXmlUrlProvider; -import sonia.scm.url.UrlProvider; -import sonia.scm.url.UrlProviderFactory; -import sonia.scm.url.WebUIUrlProvider; +import sonia.scm.url.*; import sonia.scm.user.DefaultUserManager; import sonia.scm.user.UserDAO; import sonia.scm.user.UserManager; @@ -223,7 +196,9 @@ public class ScmServletModule extends ServletModule ScmConfiguration config = getScmConfiguration(); CipherUtil cu = CipherUtil.getInstance(); - + + bind(NamespaceStrategy.class).toProvider(NamespaceStrategyProvider.class); + // bind repository provider ThrowingProviderBinder.create(binder()).bind( RepositoryProvider.class, Repository.class).to( @@ -351,10 +326,10 @@ public class ScmServletModule extends ServletModule // bind events // bind(LastModifiedUpdateListener.class); - Class namespaceStrategy = extensionProcessor.byExtensionPoint(NamespaceStrategy.class).iterator().next(); - bind(NamespaceStrategy.class, namespaceStrategy); + } + /** * Method description * diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceStrategy.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceStrategy.java index ce6362ee9c..e68d03909b 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceStrategy.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultNamespaceStrategy.java @@ -1,11 +1,18 @@ package sonia.scm.repository; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; import sonia.scm.plugin.Extension; +import sonia.scm.user.User; + @Extension -public class DefaultNamespaceStrategy implements NamespaceStrategy{ +public class DefaultNamespaceStrategy implements NamespaceStrategy { + @Override public String getNamespace() { - return "42"; + Subject subject = SecurityUtils.getSubject(); + String displayName = subject.getPrincipals().oneByType(User.class).getName(); + return displayName; } } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java index 00271a75cb..363cc4c0bb 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -42,29 +42,14 @@ import com.google.inject.Singleton; import org.apache.shiro.concurrent.SubjectAwareExecutorService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.ArgumentIsInvalidException; -import sonia.scm.ConfigurationException; -import sonia.scm.HandlerEventType; -import sonia.scm.SCMContextProvider; -import sonia.scm.Type; +import sonia.scm.*; import sonia.scm.config.ScmConfiguration; import sonia.scm.security.KeyGenerator; -import sonia.scm.util.AssertUtil; -import sonia.scm.util.CollectionAppender; -import sonia.scm.util.HttpUtil; -import sonia.scm.util.IOUtil; -import sonia.scm.util.Util; +import sonia.scm.util.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; @@ -128,16 +113,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { } } - /** - * Method description - * - * - * @param repository - * @param initRepository - * - * @throws IOException - * @throws RepositoryException - */ public void create(Repository repository, boolean initRepository) throws RepositoryException, IOException { logger.info("create repository {} of type {}", repository.getName(), @@ -163,30 +138,12 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { fireEvent(HandlerEventType.CREATE, repository); } - /** - * Method description - * - * - * @param repository - * - * @throws IOException - * @throws RepositoryException - */ @Override public void create(Repository repository) throws RepositoryException, IOException { create(repository, true); } - /** - * Method description - * - * - * @param repository - * - * @throws IOException - * @throws RepositoryException - */ @Override public void delete(Repository repository) throws RepositoryException, IOException { @@ -213,40 +170,16 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { } } - /** - * Method description - * - * - * @param repository - * - * @throws IOException - * @throws RepositoryException - */ @Override public void importRepository(Repository repository) throws RepositoryException, IOException { create(repository, false); } - /** - * Method description - * - * - * @param context - */ @Override public void init(SCMContextProvider context) { } - /** - * Method description - * - * - * @param repository - * - * @throws IOException - * @throws RepositoryException - */ @Override public void modify(Repository repository) throws RepositoryException, IOException { @@ -273,15 +206,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { } } - /** - * Method description - * - * - * @param repository - * - * @throws IOException - * @throws RepositoryException - */ @Override public void refresh(Repository repository) throws RepositoryException, IOException { @@ -299,16 +223,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { } } - //~--- get methods ---------------------------------------------------------- - /** - * Method description - * - * - * @param id - * - * @return - */ @Override public Repository get(String id) { AssertUtil.assertIsNotEmpty(id); @@ -324,15 +239,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { return repository; } - /** - * Method description - * - * - * @param type - * @param name - * - * @return - */ @Override public Repository get(String type, String name) { AssertUtil.assertIsNotEmpty(type); @@ -348,14 +254,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { return repository; } - /** - * Method description - * - * - * - * @param comparator - * @return - */ @Override public Collection getAll(Comparator comparator) { List repositories = Lists.newArrayList(); @@ -378,28 +276,12 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { return repositories; } - /** - * Method description - * - * - * @return - */ @Override public Collection getAll() { return getAll(null); } - /** - * Method description - * - * - * - * @param comparator - * @param start - * @param limit - * - * @return - */ + @Override public Collection getAll(Comparator comparator, int start, int limit) { @@ -417,26 +299,11 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { }, start, limit); } - /** - * Method description - * - * - * @param start - * @param limit - * - * @return - */ @Override public Collection getAll(int start, int limit) { return getAll(null, start, limit); } - /** - * Method description - * - * - * @return - */ @Override public Collection getConfiguredTypes() { List validTypes = Lists.newArrayList(); @@ -450,14 +317,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { return validTypes; } - /** - * Method description - * - * - * @param request - * - * @return - */ @Override public Repository getFromRequest(HttpServletRequest request) { AssertUtil.assertIsNotNull(request); @@ -465,15 +324,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { return getFromUri(HttpUtil.getStrippedURI(request)); } - /** - * Method description - * - * - * @param type - * @param uri - * - * @return - */ @Override public Repository getFromTypeAndUri(String type, String uri) { if (Strings.isNullOrEmpty(type)) { @@ -512,14 +362,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { return repository; } - /** - * Method description - * - * - * @param uri - * - * @return - */ @Override public Repository getFromUri(String uri) { AssertUtil.assertIsNotEmpty(uri); @@ -541,51 +383,21 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { return repository; } - /** - * Method description - * - * - * @param type - * - * @return - */ @Override public RepositoryHandler getHandler(String type) { return handlerMap.get(type); } - /** - * Method description - * - * - * @return - */ @Override public Long getLastModified() { return repositoryDAO.getLastModified(); } - /** - * Method description - * - * - * @return - */ @Override public Collection getTypes() { return types; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * - * @param contextProvider - * @param handler - */ private void addHandler(SCMContextProvider contextProvider, RepositoryHandler handler) { AssertUtil.assertIsNotNull(handler); @@ -609,19 +421,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { types.add(type); } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param repository - * - * @return - * - * - * @throws RepositoryException - */ private RepositoryHandler getHandler(Repository repository) throws RepositoryException { String type = repository.getType(); diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java index 95cbd44340..81059c1247 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java @@ -34,11 +34,7 @@ import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; import com.google.inject.Provider; import org.apache.shiro.SecurityUtils; -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.authc.AuthenticationInfo; -import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.authc.SimpleAuthenticationInfo; -import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authc.*; import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.mgt.DefaultSecurityManager; @@ -61,12 +57,7 @@ import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.security.KeyGenerator; import sonia.scm.user.UserTestData; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.TimeUnit; import static org.mockito.Mockito.mock; @@ -96,8 +87,6 @@ public class DefaultRepositoryManagerPerfTest { private final KeyGenerator keyGenerator = new DefaultKeyGenerator(); - private final NamespaceStrategy namespaceStrategy = new DefaultNamespaceStrategy(); - @Mock private RepositoryHandler repositoryHandler; @@ -114,7 +103,7 @@ public class DefaultRepositoryManagerPerfTest { when(repositoryHandler.getType()).thenReturn(new Type(REPOSITORY_TYPE, REPOSITORY_TYPE)); Set handlerSet = ImmutableSet.of(repositoryHandler); RepositoryMatcher repositoryMatcher = new RepositoryMatcher(Collections.emptySet()); - + NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class); repositoryManager = new DefaultRepositoryManager( configuration, contextProvider, @@ -132,10 +121,7 @@ public class DefaultRepositoryManagerPerfTest { ThreadContext.bind(securityManager); } - - /** - * Tear down test objects. - */ + @After public void tearDown(){ ThreadContext.unbindSecurityManager(); diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java index a66bf222cb..7bbdd657ee 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -58,25 +58,11 @@ import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.JAXBConfigurationStoreFactory; import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.Stack; +import java.util.*; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasProperty; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; //~--- JDK imports ------------------------------------------------------------ @@ -492,12 +478,15 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase Date: Wed, 4 Jul 2018 16:43:46 +0200 Subject: [PATCH 009/186] add restentpoint for login/logout, restructuring of modules and components, add flow usage --- .hgignore | 1 + pom.xml | 1 + scm-ui/flow-typed/npm/history_v4.x.x.js | 128 +++++++++ scm-ui/package.json | 21 +- scm-ui/pom.xml | 18 ++ scm-ui/src/App.js | 20 -- scm-ui/src/apiclient.js | 64 +++++ scm-ui/src/containers/App.js | 35 +++ scm-ui/src/{ => containers}/Login.js | 0 scm-ui/src/{ => containers}/Main.js | 4 +- scm-ui/src/{ => containers}/Navigation.js | 0 scm-ui/src/createReduxStore.js | 17 +- scm-ui/src/index.js | 41 +-- .../containers/Repositories.js | 20 +- .../modules/repositories.js | 1 - scm-ui/src/{ => users}/containers/Users.js | 14 +- scm-ui/src/{ => users}/modules/users.js | 3 - .../v2/resources/AuthenticationResource.java | 269 ++++++++++++++++++ .../java/sonia/scm/filter/SecurityFilter.java | 8 +- .../sonia/scm/security/SecurityRequests.java | 24 ++ .../web/security/ApiAuthenticationFilter.java | 19 +- .../scm/security/SecurityRequestsTest.java | 37 +++ 22 files changed, 646 insertions(+), 99 deletions(-) create mode 100644 scm-ui/flow-typed/npm/history_v4.x.x.js create mode 100644 scm-ui/pom.xml delete mode 100644 scm-ui/src/App.js create mode 100644 scm-ui/src/apiclient.js create mode 100644 scm-ui/src/containers/App.js rename scm-ui/src/{ => containers}/Login.js (100%) rename scm-ui/src/{ => containers}/Main.js (85%) rename scm-ui/src/{ => containers}/Navigation.js (100%) rename scm-ui/src/{ => repositories}/containers/Repositories.js (79%) rename scm-ui/src/{ => repositories}/modules/repositories.js (99%) rename scm-ui/src/{ => users}/containers/Users.js (81%) rename scm-ui/src/{ => users}/modules/users.js (96%) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/SecurityRequests.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/SecurityRequestsTest.java diff --git a/.hgignore b/.hgignore index 4e95a4bedc..d7152e7a57 100644 --- a/.hgignore +++ b/.hgignore @@ -35,3 +35,4 @@ scm-ui/yarn.lock scm-ui/.gitignore scm-ui/package-lock.json node_modules +scm-ui/.flowconfig diff --git a/pom.xml b/pom.xml index 2416371d48..7ed637f0d7 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,7 @@ scm-test scm-plugins scm-dao-xml + scm-ui scm-webapp scm-server scm-clients diff --git a/scm-ui/flow-typed/npm/history_v4.x.x.js b/scm-ui/flow-typed/npm/history_v4.x.x.js new file mode 100644 index 0000000000..04061f029c --- /dev/null +++ b/scm-ui/flow-typed/npm/history_v4.x.x.js @@ -0,0 +1,128 @@ +// flow-typed signature: eb8bd974b677b08dfca89de9ac05b60b +// flow-typed version: 43b30482ac/history_v4.x.x/flow_>=v0.25.x + +declare module "history/createBrowserHistory" { + declare function Unblock(): void; + + declare export type Action = "PUSH" | "REPLACE" | "POP"; + + declare export type BrowserLocation = { + pathname: string, + search: string, + hash: string, + // Browser and Memory specific + state: string, + key: string, + }; + + declare export type BrowserHistory = { + length: number, + location: BrowserLocation, + action: Action, + push: (path: string, Array) => void, + replace: (path: string, Array) => void, + go: (n: number) => void, + goBack: () => void, + goForward: () => void, + listen: Function, + block: (message: string) => Unblock, + block: ((location: BrowserLocation, action: Action) => string) => Unblock, + push: (path: string) => void, + replace: (path: string) => void, + }; + + declare type HistoryOpts = { + basename?: string, + forceRefresh?: boolean, + getUserConfirmation?: ( + message: string, + callback: (willContinue: boolean) => void, + ) => void, + }; + + declare export default (opts?: HistoryOpts) => BrowserHistory; +} + +declare module "history/createMemoryHistory" { + declare function Unblock(): void; + + declare export type Action = "PUSH" | "REPLACE" | "POP"; + + declare export type MemoryLocation = { + pathname: string, + search: string, + hash: string, + // Browser and Memory specific + state: string, + key: string, + }; + + declare export type MemoryHistory = { + length: number, + location: MemoryLocation, + action: Action, + index: number, + entries: Array, + push: (path: string, Array) => void, + replace: (path: string, Array) => void, + go: (n: number) => void, + goBack: () => void, + goForward: () => void, + // Memory only + canGo: (n: number) => boolean, + listen: Function, + block: (message: string) => Unblock, + block: ((location: MemoryLocation, action: Action) => string) => Unblock, + push: (path: string) => void, + }; + + declare type HistoryOpts = { + initialEntries?: Array, + initialIndex?: number, + keyLength?: number, + getUserConfirmation?: ( + message: string, + callback: (willContinue: boolean) => void, + ) => void, + }; + + declare export default (opts?: HistoryOpts) => MemoryHistory; +} + +declare module "history/createHashHistory" { + declare function Unblock(): void; + + declare export type Action = "PUSH" | "REPLACE" | "POP"; + + declare export type HashLocation = { + pathname: string, + search: string, + hash: string, + }; + + declare export type HashHistory = { + length: number, + location: HashLocation, + action: Action, + push: (path: string, Array) => void, + replace: (path: string, Array) => void, + go: (n: number) => void, + goBack: () => void, + goForward: () => void, + listen: Function, + block: (message: string) => Unblock, + block: ((location: HashLocation, action: Action) => string) => Unblock, + push: (path: string) => void, + }; + + declare type HistoryOpts = { + basename?: string, + hashType: "slash" | "noslash" | "hashbang", + getUserConfirmation?: ( + message: string, + callback: (willContinue: boolean) => void, + ) => void, + }; + + declare export default (opts?: HistoryOpts) => HashHistory; +} diff --git a/scm-ui/package.json b/scm-ui/package.json index c56f88f9e1..8513981bf5 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -3,25 +3,30 @@ "version": "0.1.0", "private": true, "dependencies": { - "ces-theme": "https://github.com/cloudogu/ces-theme.git", "classnames": "^2.2.5", + "flow-bin": "^0.75.0", + "history": "^4.7.2", "react": "^16.4.1", "react-dom": "^16.4.1", "react-jss": "^8.6.0", "react-redux": "^5.0.7", - "react-scripts": "1.1.4", - "redux": "^4.0.0", - "redux-logger": "^3.0.6", - "redux-thunk": "^2.3.0", - "history": "^4.7.2", "react-router-dom": "^4.3.1", "react-router-redux": "^5.0.0-alpha.9", - "redux-devtools-extension": "^2.13.5" + "react-scripts": "1.1.4", + "redux": "^4.0.0", + "redux-devtools-extension": "^2.13.5", + "redux-logger": "^3.0.6", + "redux-thunk": "^2.3.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "flow": "flow" + }, + "proxy": "http://localhost:8081/scm", + "devDependencies": { + "prettier": "^1.13.7" } } diff --git a/scm-ui/pom.xml b/scm-ui/pom.xml new file mode 100644 index 0000000000..aa80884abc --- /dev/null +++ b/scm-ui/pom.xml @@ -0,0 +1,18 @@ + + + + 4.0.0 + + + sonia.scm + scm + 2.0.0-SNAPSHOT + + + sonia.scm.clients + scm-ui + pom + 2.0.0-SNAPSHOT + scm-ui + + diff --git a/scm-ui/src/App.js b/scm-ui/src/App.js deleted file mode 100644 index 79083246bd..0000000000 --- a/scm-ui/src/App.js +++ /dev/null @@ -1,20 +0,0 @@ -import React, { Component } from 'react'; -import Navigation from './Navigation'; -import Main from './Main'; -import {withRouter} from 'react-router-dom'; -import 'ces-theme/dist/css/ces.css'; - - - -class App extends Component { - render() { - return ( -

- -
-
- ); - } -} - -export default withRouter(App); diff --git a/scm-ui/src/apiclient.js b/scm-ui/src/apiclient.js new file mode 100644 index 0000000000..f6b04a59a1 --- /dev/null +++ b/scm-ui/src/apiclient.js @@ -0,0 +1,64 @@ +// @flow + +// get api base url from environment +const apiUrl = process.env.API_URL || process.env.PUBLIC_URL || ""; + +export const PAGE_NOT_FOUND_ERROR = Error("page not found"); + +// fetch does not send the X-Requested-With header (https://github.com/github/fetch/issues/17), +// but we need the header to detect ajax request (AjaxAwareAuthenticationRedirectStrategy). +const fetchOptions: RequestOptions = { + credentials: "same-origin", + headers: { + "X-Requested-With": "XMLHttpRequest" + } +}; + +function handleStatusCode(response: Response) { + if (!response.ok) { + if (response.status === 401) { + return response; + } + if (response.status === 404) { + throw PAGE_NOT_FOUND_ERROR; + } + throw new Error("server returned status code " + response.status); + } + return response; +} + +function createUrl(url: string) { + return `${apiUrl}/api/rest/v2/${url}`; +} + +class ApiClient { + get(url: string) { + return fetch(createUrl(url), fetchOptions).then(handleStatusCode); + } + + post(url: string, payload: any) { + return this.httpRequestWithJSONBody(url, payload, "POST"); + } + + delete(url: string, payload: any) { + let options: RequestOptions = { + method: "DELETE" + }; + options = Object.assign(options, fetchOptions); + return fetch(createUrl(url), options).then(handleStatusCode); + } + + httpRequestWithJSONBody(url: string, payload: any, method: string) { + let options: RequestOptions = { + method: method, + body: JSON.stringify(payload) + }; + options = Object.assign(options, fetchOptions); + // $FlowFixMe + options.headers["Content-Type"] = "application/json"; + + return fetch(createUrl(url), options).then(handleStatusCode); + } +} + +export let apiClient = new ApiClient(); diff --git a/scm-ui/src/containers/App.js b/scm-ui/src/containers/App.js new file mode 100644 index 0000000000..e03221ec6a --- /dev/null +++ b/scm-ui/src/containers/App.js @@ -0,0 +1,35 @@ +import React, { Component } from "react"; +import Navigation from "./Navigation"; +import Main from "./Main"; +import Login from "./Login"; +import { withRouter } from "react-router-dom"; + +type Props = { + login: boolean +} + +class App extends Component { + + render() { + + const { login} = this.props; + + if(login) { + return ( +
+ +
+ ); + } + else { + return ( +
+ +
+
+ ); + } + } +} + +export default withRouter(App); diff --git a/scm-ui/src/Login.js b/scm-ui/src/containers/Login.js similarity index 100% rename from scm-ui/src/Login.js rename to scm-ui/src/containers/Login.js diff --git a/scm-ui/src/Main.js b/scm-ui/src/containers/Main.js similarity index 85% rename from scm-ui/src/Main.js rename to scm-ui/src/containers/Main.js index 10e7d4f0c2..8f15455078 100644 --- a/scm-ui/src/Main.js +++ b/scm-ui/src/containers/Main.js @@ -5,8 +5,8 @@ import classNames from 'classnames'; import { Route, withRouter } from 'react-router'; -import Repositories from './containers/Repositories'; -import Users from './containers/Users'; +import Repositories from '../repositories/containers/Repositories'; +import Users from '../users/containers/Users'; import {Switch} from 'react-router-dom'; const styles = { diff --git a/scm-ui/src/Navigation.js b/scm-ui/src/containers/Navigation.js similarity index 100% rename from scm-ui/src/Navigation.js rename to scm-ui/src/containers/Navigation.js diff --git a/scm-ui/src/createReduxStore.js b/scm-ui/src/createReduxStore.js index 5077df3e54..502fc55faf 100644 --- a/scm-ui/src/createReduxStore.js +++ b/scm-ui/src/createReduxStore.js @@ -1,12 +1,15 @@ -import thunk from 'redux-thunk'; -import logger from 'redux-logger'; -import { createStore, compose, applyMiddleware, combineReducers } from 'redux'; -import { routerReducer, routerMiddleware } from 'react-router-redux'; +// @flow +import thunk from "redux-thunk"; +import logger from "redux-logger"; +import { createStore, compose, applyMiddleware, combineReducers } from "redux"; +import { routerReducer, routerMiddleware } from "react-router-redux"; -import repositories from './modules/repositories'; -import users from './modules/users'; +import repositories from "./repositories/modules/repositories"; +import users from "./users/modules/users"; -function createReduxStore(history) { +import type {BrowserHistory} from "history/createBrowserHistory"; + +function createReduxStore(history: BrowserHistory) { const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const reducer = combineReducers({ diff --git a/scm-ui/src/index.js b/scm-ui/src/index.js index 12d8f58f94..564249494a 100644 --- a/scm-ui/src/index.js +++ b/scm-ui/src/index.js @@ -1,32 +1,41 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; -import registerServiceWorker from './registerServiceWorker'; +// @flow +import React from "react"; +import ReactDOM from "react-dom"; +import App from "./containers/App"; +import registerServiceWorker from "./registerServiceWorker"; -import { Provider } from 'react-redux'; -import createHistory from 'history/createBrowserHistory'; -import createReduxStore from './createReduxStore'; -import { ConnectedRouter } from 'react-router-redux'; +import { Provider } from "react-redux"; +import createHistory from "history/createBrowserHistory"; +import type { BrowserHistory } from "history/createBrowserHistory"; + +import createReduxStore from "./createReduxStore"; +import { ConnectedRouter } from "react-router-redux"; + +const publicUrl: string = process.env.PUBLIC_URL || ""; // Create a history of your choosing (we're using a browser history in this case) -const history = createHistory({ - basename: process.env.PUBLIC_URL +const history: BrowserHistory = createHistory({ + basename: publicUrl }); -window.appHistory = history; // Add the reducer to your store on the `router` key // Also apply our middleware for navigating const store = createReduxStore(history); +const root = document.getElementById("root"); +if (!root) { + throw new Error("could not find root element"); +} + ReactDOM.render( - { /* ConnectedRouter will use the store from Provider automatically */} - - - + {/* ConnectedRouter will use the store from Provider automatically */} + + + , - document.getElementById('root') + root ); registerServiceWorker(); diff --git a/scm-ui/src/containers/Repositories.js b/scm-ui/src/repositories/containers/Repositories.js similarity index 79% rename from scm-ui/src/containers/Repositories.js rename to scm-ui/src/repositories/containers/Repositories.js index cb4aca4373..5df04cbc4c 100644 --- a/scm-ui/src/containers/Repositories.js +++ b/scm-ui/src/repositories/containers/Repositories.js @@ -3,12 +3,14 @@ import React from 'react'; import { connect } from 'react-redux'; import { fetchRepositoriesIfNeeded } from '../modules/repositories'; -import Login from '../Login'; +import Login from '../../containers/Login'; type Props = { login: boolean, - error: any, + error: Error, + repositories: any, + fetchRepositoriesIfNeeded: () => void } class Repositories extends React.Component { @@ -21,16 +23,7 @@ class Repositories extends React.Component { const { login, error, repositories } = this.props; - if(login) { - return ( -
-

SCM

- -
- ); - } - else if(!login){ - return ( + return (

SCM

Startpage

@@ -38,8 +31,7 @@ class Repositories extends React.Component { Users hier!
- ); - } + ) } diff --git a/scm-ui/src/modules/repositories.js b/scm-ui/src/repositories/modules/repositories.js similarity index 99% rename from scm-ui/src/modules/repositories.js rename to scm-ui/src/repositories/modules/repositories.js index c58f613330..e4ab290a44 100644 --- a/scm-ui/src/modules/repositories.js +++ b/scm-ui/src/repositories/modules/repositories.js @@ -1,4 +1,3 @@ -//@flow const FETCH_REPOSITORIES = 'scm/repositories/FETCH'; const FETCH_REPOSITORIES_SUCCESS = 'scm/repositories/FETCH_SUCCESS'; const FETCH_REPOSITORIES_FAILURE = 'scm/repositories/FETCH_FAILURE'; diff --git a/scm-ui/src/containers/Users.js b/scm-ui/src/users/containers/Users.js similarity index 81% rename from scm-ui/src/containers/Users.js rename to scm-ui/src/users/containers/Users.js index 1e4b0bfba8..e3f3c5190d 100644 --- a/scm-ui/src/containers/Users.js +++ b/scm-ui/src/users/containers/Users.js @@ -3,7 +3,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { fetchUsersIfNeeded } from '../modules/users'; -import Login from '../Login'; +import Login from '../../containers/Login'; type Props = { login: boolean, @@ -22,23 +22,13 @@ class Users extends React.Component { const { login, error, users } = this.props; - - if(login) { - return ( -
-

SCM

- -
- ); - } - else if(!login){ return (

SCM

Users

); - } + } } diff --git a/scm-ui/src/modules/users.js b/scm-ui/src/users/modules/users.js similarity index 96% rename from scm-ui/src/modules/users.js rename to scm-ui/src/users/modules/users.js index 2afdfdfd8a..ed92f7ba10 100644 --- a/scm-ui/src/modules/users.js +++ b/scm-ui/src/users/modules/users.js @@ -1,10 +1,7 @@ -//@flow const FETCH_USERS = 'scm/users/FETCH'; const FETCH_USERS_SUCCESS= 'scm/users/FETCH_SUCCESS'; const FETCH_USERS_FAILURE = 'scm/users/FETCH_FAILURE'; -const THRESHOLD_TIMESTAMP = 10000; - function requestUsers() { return { type: FETCH_USERS diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java new file mode 100644 index 0000000000..ed9733fdef --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AuthenticationResource.java @@ -0,0 +1,269 @@ +package sonia.scm.api.v2.resources; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.inject.Inject; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.DisabledAccountException; +import org.apache.shiro.authc.ExcessiveAttemptsException; +import org.apache.shiro.subject.Subject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.api.rest.RestActionResult; +import sonia.scm.security.*; +import sonia.scm.util.HttpUtil; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.List; + +/** + * Created by masuewer on 04.07.18. + */ +@Path(AuthenticationResource.PATH) +public class AuthenticationResource { + + private static final Logger LOG = LoggerFactory.getLogger(AuthenticationResource.class); + + public static final String PATH = "v2/auth"; + + private final AccessTokenBuilderFactory tokenBuilderFactory; + private final AccessTokenCookieIssuer cookieIssuer; + + @Inject + public AuthenticationResource(AccessTokenBuilderFactory tokenBuilderFactory, AccessTokenCookieIssuer cookieIssuer) + { + this.tokenBuilderFactory = tokenBuilderFactory; + this.cookieIssuer = cookieIssuer; + } + + + @POST + @Path("access_token") + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, required parameter is missing"), + @ResponseCode(code = 401, condition = "unauthorized, the specified username or password is wrong"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response authenticateViaForm( + @Context HttpServletRequest request, + @Context HttpServletResponse response, + @BeanParam AuthenticationRequest authentication + ) { + return authenticate(request, response, authentication); + } + + @POST + @Path("access_token") + @Consumes(MediaType.APPLICATION_JSON) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, required parameter is missing"), + @ResponseCode(code = 401, condition = "unauthorized, the specified username or password is wrong"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response authenticateViaJSONBody( + @Context HttpServletRequest request, + @Context HttpServletResponse response, + AuthenticationRequest authentication + ) { + return authenticate(request, response, authentication); + } + + private Response authenticate( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationRequest authentication + ) { + authentication.validate(); + + Response res; + Subject subject = SecurityUtils.getSubject(); + + try + { + subject.login(Tokens.createAuthenticationToken(request, authentication.getUsername(), authentication.getPassword())); + + AccessTokenBuilder tokenBuilder = tokenBuilderFactory.create(); + if ( authentication.getScope() != null ) { + tokenBuilder.scope(Scope.valueOf(authentication.getScope())); + } + + AccessToken token = tokenBuilder.build(); + + if (authentication.isCookie()) { + cookieIssuer.authenticate(request, response, token); + res = Response.noContent().build(); + } else { + res = Response.ok( token.compact() ).build(); + } + } + catch (DisabledAccountException ex) + { + if (LOG.isTraceEnabled()) + { + LOG.trace( + "authentication failed, account user ".concat(authentication.getUsername()).concat( + " is locked"), ex); + } + else + { + LOG.warn("authentication failed, account {} is locked", authentication.getUsername()); + } + + res = handleFailedAuthentication(request, ex, Response.Status.FORBIDDEN, + WUIAuthenticationFailure.LOCKED); + } + catch (ExcessiveAttemptsException ex) + { + if (LOG.isTraceEnabled()) + { + LOG.trace( + "authentication failed, account user ".concat(authentication.getUsername()).concat( + " is temporary locked"), ex); + } + else + { + LOG.warn("authentication failed, account {} is temporary locked", authentication.getUsername()); + } + + res = handleFailedAuthentication(request, ex, Response.Status.FORBIDDEN, + WUIAuthenticationFailure.TEMPORARY_LOCKED); + } + catch (AuthenticationException ex) + { + if (LOG.isTraceEnabled()) + { + LOG.trace("authentication failed for user ".concat(authentication.getUsername()), ex); + } + else + { + LOG.warn("authentication failed for user {}", authentication.getUsername()); + } + + res = handleFailedAuthentication(request, ex, Response.Status.UNAUTHORIZED, + WUIAuthenticationFailure.WRONG_CREDENTIALS); + } + + return res; + } + + @DELETE + @Path("access_token") + @StatusCodes({ + @ResponseCode(code = 204, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response logout(@Context HttpServletRequest request, @Context HttpServletResponse response) + { + Subject subject = SecurityUtils.getSubject(); + + subject.logout(); + + // remove authentication cookie + cookieIssuer.invalidate(request, response); + + // TODO anonymous access ?? + return Response.noContent().build(); + } + + public static class AuthenticationRequest { + + @FormParam("grant_type") + @JsonProperty("grant_type") + private String grantType; + + @FormParam("username") + private String username; + + @FormParam("password") + private String password; + + @FormParam("cookie") + private boolean cookie; + + @FormParam("scope") + private List scope; + + public String getGrantType() { + return grantType; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public boolean isCookie() { + return cookie; + } + + public List getScope() { + return scope; + } + + public void validate() { + Preconditions.checkArgument(!Strings.isNullOrEmpty(grantType), "grant_type parameter is required"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(username), "username parameter is required"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(password), "password parameter is required"); + } + } + + + private Response handleFailedAuthentication(HttpServletRequest request, + AuthenticationException ex, Response.Status status, + WUIAuthenticationFailure failure) { + Response response; + + if (HttpUtil.isWUIRequest(request)) { + response = Response.ok(new WUIAuthenticationFailedResult(failure, + ex.getMessage())).build(); + } else { + response = Response.status(status).build(); + } + + return response; + } + + private enum WUIAuthenticationFailure { LOCKED, TEMPORARY_LOCKED, WRONG_CREDENTIALS } + + @XmlRootElement(name = "result") + @XmlAccessorType(XmlAccessType.FIELD) + private static final class WUIAuthenticationFailedResult extends RestActionResult { + + private final WUIAuthenticationFailure failure; + private final String message; + + public WUIAuthenticationFailedResult(WUIAuthenticationFailure failure, String message) { + super(false); + this.failure = failure; + this.message = message; + } + + public WUIAuthenticationFailure getFailure() { + return failure; + } + + public String getMessage() { + return message; + } + + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java index e94b6a3aee..07475e5853 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java @@ -44,6 +44,7 @@ import org.apache.shiro.subject.Subject; import sonia.scm.Priority; import sonia.scm.SCMContext; import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.SecurityRequests; import sonia.scm.web.filter.HttpFilter; import sonia.scm.web.filter.SecurityHttpServletRequestWrapper; @@ -72,6 +73,8 @@ public class SecurityFilter extends HttpFilter /** Field description */ public static final String URL_AUTHENTICATION = "/api/rest/auth"; + public static final String URLV2_AUTHENTICATION = "/api/rest/v2/auth"; + //~--- constructors --------------------------------------------------------- /** @@ -104,10 +107,7 @@ public class SecurityFilter extends HttpFilter HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - String uri = - request.getRequestURI().substring(request.getContextPath().length()); - - if (!uri.startsWith(URL_AUTHENTICATION)) + if (!SecurityRequests.isAuthenticationRequest(request)) { Subject subject = SecurityUtils.getSubject(); if (hasPermission(subject)) diff --git a/scm-webapp/src/main/java/sonia/scm/security/SecurityRequests.java b/scm-webapp/src/main/java/sonia/scm/security/SecurityRequests.java new file mode 100644 index 0000000000..225767cd3b --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/SecurityRequests.java @@ -0,0 +1,24 @@ +package sonia.scm.security; + +import javax.servlet.http.HttpServletRequest; +import java.util.regex.Pattern; + +/** + * Created by masuewer on 04.07.18. + */ +public final class SecurityRequests { + + private static final Pattern URI_LOGIN_PATTERN = Pattern.compile("/api/rest(?:/v2)?/auth/access_token"); + + private SecurityRequests() {} + + public static boolean isAuthenticationRequest(HttpServletRequest request) { + String uri = request.getRequestURI().substring(request.getContextPath().length()); + return isAuthenticationRequest(uri); + } + + public static boolean isAuthenticationRequest(String uri) { + return URI_LOGIN_PATTERN.matcher(uri).matches(); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java b/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java index 8340225872..d8fe469af9 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/ApiAuthenticationFilter.java @@ -36,24 +36,22 @@ package sonia.scm.web.security; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; - import sonia.scm.Priority; import sonia.scm.config.ScmConfiguration; import sonia.scm.filter.Filters; import sonia.scm.filter.WebElement; -import sonia.scm.web.filter.AuthenticationFilter; +import sonia.scm.security.SecurityRequests; import sonia.scm.web.WebTokenGenerator; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.IOException; - -import java.util.Set; +import sonia.scm.web.filter.AuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +//~--- JDK imports ------------------------------------------------------------ /** * Filter to handle authentication for the rest api of SCM-Manager. @@ -66,9 +64,6 @@ import javax.servlet.http.HttpServletResponse; public class ApiAuthenticationFilter extends AuthenticationFilter { - /** login uri */ - public static final String URI_LOGIN = "/api/rest/auth/access_token"; - //~--- constructors --------------------------------------------------------- /** @@ -104,7 +99,7 @@ public class ApiAuthenticationFilter extends AuthenticationFilter throws IOException, ServletException { // skip filter on login resource - if (request.getRequestURI().contains(URI_LOGIN)) + if (SecurityRequests.isAuthenticationRequest(request)) { chain.doFilter(request, response); } diff --git a/scm-webapp/src/test/java/sonia/scm/security/SecurityRequestsTest.java b/scm-webapp/src/test/java/sonia/scm/security/SecurityRequestsTest.java new file mode 100644 index 0000000000..9e6d54dc0b --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/SecurityRequestsTest.java @@ -0,0 +1,37 @@ +package sonia.scm.security; + +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; + +/** + * Created by masuewer on 04.07.18. + */ +@RunWith(MockitoJUnitRunner.class) +public class SecurityRequestsTest { + + @Mock + private HttpServletRequest request; + + @Test + public void testIsAuthenticationRequestWithContextPath() { + when(request.getRequestURI()).thenReturn("/scm/api/rest/auth/access_token"); + when(request.getContextPath()).thenReturn("/scm"); + + assertTrue(SecurityRequests.isAuthenticationRequest(request)); + } + + @Test + public void testIsAuthenticationRequest() throws Exception { + assertTrue(SecurityRequests.isAuthenticationRequest("/api/rest/auth/access_token")); + assertTrue(SecurityRequests.isAuthenticationRequest("/api/rest/v2/auth/access_token")); + assertFalse(SecurityRequests.isAuthenticationRequest("/api/rest/repositories")); + assertFalse(SecurityRequests.isAuthenticationRequest("/api/rest/v2/repositories")); + } +} From fbfebe1df7fcfdc2d13d4ecba6bd82347a0e6b43 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Thu, 5 Jul 2018 16:48:56 +0200 Subject: [PATCH 010/186] Bootstrapped login in UI --- scm-ui/src/containers/App.js | 25 ++++++------ scm-ui/src/containers/Login.js | 70 ++++++++++++++++++++++++++------- scm-ui/src/createReduxStore.js | 9 +++-- scm-ui/src/modules/login.js | 71 ++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 30 deletions(-) create mode 100644 scm-ui/src/modules/login.js diff --git a/scm-ui/src/containers/App.js b/scm-ui/src/containers/App.js index e03221ec6a..b30ca75d0d 100644 --- a/scm-ui/src/containers/App.js +++ b/scm-ui/src/containers/App.js @@ -6,29 +6,26 @@ import { withRouter } from "react-router-dom"; type Props = { login: boolean -} +}; class App extends Component { - render() { + const { login } = this.props; - const { login} = this.props; - - if(login) { + if (!login) { return (
- + +
+ ); + } else { + return ( +
+ +
); } - else { - return ( -
- -
-
- ); - } } } diff --git a/scm-ui/src/containers/Login.js b/scm-ui/src/containers/Login.js index f85fbef9ed..fb20d48366 100644 --- a/scm-ui/src/containers/Login.js +++ b/scm-ui/src/containers/Login.js @@ -1,24 +1,35 @@ //@flow -import React from 'react'; -import injectSheet from 'react-jss'; +import React from "react"; +import injectSheet from "react-jss"; +import { login } from "../modules/login"; +import { connect } from "react-redux"; const styles = { wrapper: { - width: '100%', - display: 'flex', - height: '10em' + width: "100%", + display: "flex", + height: "10em" }, login: { - margin: 'auto', - textAlign: 'center' + margin: "auto", + textAlign: "center" } }; -type Props = { - classes: any; -} - class Login extends React.Component { + state = {}; + handleUsernameChange(event) { + this.setState({ username: event.target.value }); + } + + handlePasswordChange(event) { + this.setState({ password: event.target.value }); + } + + handleSubmit(event) { + event.preventDefault(); + this.props.login(this.state.username, this.state.password); + } render() { const { classes } = this.props; @@ -26,11 +37,44 @@ class Login extends React.Component {
You need to log in! ... +
+ + + +
); } - } -export default injectSheet(styles)(Login); +const mapStateToProps = state => { + return {}; +}; + +const mapDispatchToProps = dispatch => { + return { + login: (username: string, password: string) => + dispatch(login(username, password)) + }; +}; + +const StyledLogin = injectSheet(styles)( + connect( + mapStateToProps, + mapDispatchToProps + )(Login) +); +export default StyledLogin; +// export default connect( +// mapStateToProps, +// mapDispatchToProps +// )(StyledLogin); diff --git a/scm-ui/src/createReduxStore.js b/scm-ui/src/createReduxStore.js index 502fc55faf..df46c5be34 100644 --- a/scm-ui/src/createReduxStore.js +++ b/scm-ui/src/createReduxStore.js @@ -6,16 +6,19 @@ import { routerReducer, routerMiddleware } from "react-router-redux"; import repositories from "./repositories/modules/repositories"; import users from "./users/modules/users"; +import login from "./modules/login"; -import type {BrowserHistory} from "history/createBrowserHistory"; +import type { BrowserHistory } from "history/createBrowserHistory"; function createReduxStore(history: BrowserHistory) { - const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; + const composeEnhancers = + window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const reducer = combineReducers({ router: routerReducer, repositories, - users + users, + login }); return createStore( diff --git a/scm-ui/src/modules/login.js b/scm-ui/src/modules/login.js new file mode 100644 index 0000000000..e46abc44f4 --- /dev/null +++ b/scm-ui/src/modules/login.js @@ -0,0 +1,71 @@ +//@flow + +const LOGIN = "scm/auth/login"; +const LOGIN_REQUEST = "scm/auth/login_request"; +const LOGIN_SUCCESSFUL = "scm/auth/login_successful"; +const LOGIN_FAILED = "scm/auth/login_failed"; + +export function loginRequest() { + return { + type: LOGIN_REQUEST + }; +} + +export function login(username: string, password: string) { + var login_data = { + cookie: true, + grant_type: "password", + password: username, + username: password + }; + console.log(login_data); + return function(dispatch) { + dispatch(loginRequest()); + return fetch("/api/rest/v2/auth/access_token", { + method: "POST", + headers: { + "Content-Type": "application/json; charset=utf-8" + }, + body: JSON.stringify(login_data) + }).then( + response => { + if (response.ok) { + dispatch(loginSuccessful()); + } + }, + error => console.log("error logging in: " + error) + ); + }; +} + +export function loginSuccessful() { + return { + type: LOGIN_SUCCESSFUL + }; +} + +export default function reducer(state = {}, action = {}) { + switch (action.type) { + case LOGIN: + return { + ...state, + login: false, + error: null + }; + case LOGIN_SUCCESSFUL: + return { + ...state, + login: true, + error: null + }; + case LOGIN_FAILED: + return { + ...state, + login: false, + error: action.payload + }; + + default: + return state; + } +} From 9e85cb81a7961e1c0d636447d82d4cb822be3772 Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Fri, 6 Jul 2018 10:51:14 +0200 Subject: [PATCH 011/186] Create new branch From 40ee98dafeb522d97dfb9efff3643f3b38c3d871 Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Fri, 6 Jul 2018 11:08:52 +0200 Subject: [PATCH 012/186] Add media type for global config --- scm-core/src/main/java/sonia/scm/web/VndMediaType.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 3ec121f9a4..31ea2222c9 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -17,6 +17,8 @@ public class VndMediaType { public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX; public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX; + public static final String GLOBAL_CONFIG = PREFIX + "global_config" + SUFFIX; + private VndMediaType() { } From 5d5d3c91700022cbbcfe135b2e82e894a28dd5a8 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Fri, 6 Jul 2018 11:57:43 +0200 Subject: [PATCH 013/186] Implemented persisting repositories according to namespace changes Repository directories are now named after the repo's id instead of it's name --- .../AbstractSimpleRepositoryHandler.java | 335 ++++-------------- .../RepositoryDirectoryHandler.java | 8 - .../repository/GitRepositoryHandlerTest.java | 86 ++--- .../repository/HgRepositoryHandlerTest.java | 89 ++--- .../repository/SvnRepositoryHandlerTest.java | 99 ++++-- .../repository/DummyRepositoryHandler.java | 98 ++--- .../scm/repository/RepositoryTestData.java | 2 +- .../SimpleRepositoryHandlerTestBase.java | 132 ++----- 8 files changed, 283 insertions(+), 566 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index 670cab93e1..b61a4dce19 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -1,19 +1,19 @@ /** * 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. + * 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. + * 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. - * + * 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 @@ -24,13 +24,11 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + *

* http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- @@ -38,181 +36,118 @@ package sonia.scm.repository; import com.google.common.base.Charsets; import com.google.common.base.Throwables; import com.google.common.io.Resources; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.ConfigurationException; import sonia.scm.io.CommandResult; import sonia.scm.io.ExtendedCommand; import sonia.scm.io.FileSystem; +import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.util.IOUtil; -//~--- JDK imports ------------------------------------------------------------ - import java.io.File; import java.io.IOException; - import java.net.URL; -import sonia.scm.store.ConfigurationStoreFactory; + +//~--- JDK imports ------------------------------------------------------------ /** - * - * @author Sebastian Sdorra - * - * * @param + * @author Sebastian Sdorra */ public abstract class AbstractSimpleRepositoryHandler - extends AbstractRepositoryHandler implements RepositoryDirectoryHandler -{ + extends AbstractRepositoryHandler implements RepositoryDirectoryHandler { - /** Field description */ public static final String DEFAULT_VERSION_INFORMATION = "unknown"; - /** Field description */ public static final String DIRECTORY_REPOSITORY = "repositories"; - /** Field description */ public static final String DOT = "."; - /** the logger for AbstractSimpleRepositoryHandler */ + /** + * the logger for AbstractSimpleRepositoryHandler + */ private static final Logger logger = LoggerFactory.getLogger(AbstractSimpleRepositoryHandler.class); - //~--- constructors --------------------------------------------------------- + private FileSystem fileSystem; + - /** - * Constructs ... - * - * - * @param storeFactory - * @param fileSystem - */ public AbstractSimpleRepositoryHandler(ConfigurationStoreFactory storeFactory, - FileSystem fileSystem) - { + FileSystem fileSystem) { super(storeFactory); this.fileSystem = fileSystem; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param repository - * - * @throws IOException - * @throws RepositoryException - */ @Override public void create(Repository repository) - throws RepositoryException, IOException - { + throws RepositoryException, IOException { File directory = getDirectory(repository); - if (directory.exists()) - { + if (directory.exists()) { throw RepositoryAlreadyExistsException.create(repository); } checkPath(directory); - try - { + try { fileSystem.create(directory); create(repository, directory); postCreate(repository, directory); - } - catch (Exception ex) - { - if (directory.exists()) - { - if (logger.isDebugEnabled()) - { + } catch (Exception ex) { + if (directory.exists()) { + if (logger.isDebugEnabled()) { logger.debug( - "delete repository directory {}, because of failed repository creation", - directory); + "delete repository directory {}, because of failed repository creation", + directory); } fileSystem.destroy(directory); } Throwables.propagateIfPossible(ex, RepositoryException.class, - IOException.class); + IOException.class); } } - /** - * Method description - * - * - * - * @param repository - * @return - */ @Override - public String createResourcePath(Repository repository) - { + public String createResourcePath(Repository repository) { StringBuilder path = new StringBuilder("/"); - path.append(getType().getName()).append("/").append(repository.getName()); + path.append(getType().getName()).append("/").append(repository.getId()); return path.toString(); } - /** - * Method description - * - * - * @param repository - * - * @throws IOException - * @throws RepositoryException - */ @Override public void delete(Repository repository) - throws RepositoryException, IOException - { + throws RepositoryException, IOException { File directory = getDirectory(repository); - if (directory.exists()) - { + if (directory.exists()) { fileSystem.destroy(directory); cleanupEmptyDirectories(config.getRepositoryDirectory(), - directory.getParentFile()); - } - else if (logger.isWarnEnabled()) - { + directory.getParentFile()); + } else if (logger.isWarnEnabled()) { logger.warn("repository {} not found", repository); } } - /** - * Method description - * - */ @Override - public void loadConfig() - { + public void loadConfig() { super.loadConfig(); - if (config == null) - { + if (config == null) { config = createInitialConfig(); - if (config != null) - { + if (config != null) { File repositoryDirectory = config.getRepositoryDirectory(); - if (repositoryDirectory == null) - { + if (repositoryDirectory == null) { repositoryDirectory = new File( - baseDirectory, - DIRECTORY_REPOSITORY.concat(File.separator).concat( - getType().getName())); + baseDirectory, + DIRECTORY_REPOSITORY.concat(File.separator).concat( + getType().getName())); config.setRepositoryDirectory(repositoryDirectory); } @@ -222,108 +157,52 @@ public abstract class AbstractSimpleRepositoryHandler * 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. + * 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. + * 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. - * + * 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 @@ -24,50 +24,45 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + *

* http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import sonia.scm.io.DefaultFileSystem; - -import static org.junit.Assert.*; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; -import sonia.scm.store.ConfigurationStoreFactory; +import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.io.DefaultFileSystem; import sonia.scm.schedule.Scheduler; +import sonia.scm.store.ConfigurationStoreFactory; + +import java.io.File; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +//~--- JDK imports ------------------------------------------------------------ /** * * @author Sebastian Sdorra */ @RunWith(MockitoJUnitRunner.class) -public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase -{ +public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { @Mock private Scheduler scheduler; - - /** - * Method description - * - * - * @param directory - */ + + @Mock + private ConfigurationStoreFactory factory; + @Override - protected void checkDirectory(File directory) - { + protected void checkDirectory(File directory) { File head = new File(directory, "HEAD"); assertTrue(head.exists()); @@ -84,21 +79,12 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase assertTrue(refs.isDirectory()); } - /** - * Method description - * - * - * @param factory - * @param directory - * - * @return - */ + @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, - File directory) - { + File directory) { GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, - new DefaultFileSystem(), scheduler); + new DefaultFileSystem(), scheduler); repositoryHandler.init(contextProvider); @@ -110,4 +96,20 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase return repositoryHandler; } + + @Test + public void getDirectory() { + GitRepositoryHandler repositoryHandler = new GitRepositoryHandler(factory, + new DefaultFileSystem(), scheduler); + + GitConfig gitConfig = new GitConfig(); + gitConfig.setRepositoryDirectory(new File("/path")); + repositoryHandler.setConfig(gitConfig); + + Repository repository = new Repository("id", "git", "Name"); + + File path = repositoryHandler.getDirectory(repository); + assertEquals("/path/id", path.getAbsolutePath()); + assertTrue(path.getAbsolutePath().endsWith("id")); + } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java index c14e5f1b61..2fdc039c53 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java @@ -1,19 +1,19 @@ /** * 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. + * 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. + * 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. - * + * 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 @@ -24,43 +24,45 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + *

* http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.io.DefaultFileSystem; - -import static org.junit.Assert.*; - -//~--- JDK imports ------------------------------------------------------------ +import sonia.scm.store.ConfigurationStoreFactory; import java.io.File; -import sonia.scm.store.ConfigurationStoreFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +//~--- JDK imports ------------------------------------------------------------ /** * * @author Sebastian Sdorra */ -public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase -{ +@RunWith(MockitoJUnitRunner.class) +public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { + + @Mock + private ConfigurationStoreFactory factory; + + @Mock + private com.google.inject.Provider provider; - /** - * Method description - * - * - * @param directory - */ @Override - protected void checkDirectory(File directory) - { + protected void checkDirectory(File directory) { File hgDirectory = new File(directory, ".hg"); assertTrue(hgDirectory.exists()); @@ -73,22 +75,12 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase assertTrue(hgrc.length() > 0); } - /** - * Method description - * - * - * @param factory - * @param directory - * - * @return - */ @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, - File directory) - { + File directory) { HgRepositoryHandler handler = new HgRepositoryHandler(factory, - new DefaultFileSystem(), - new HgContextProvider()); + new DefaultFileSystem(), + new HgContextProvider()); handler.init(contextProvider); handler.getConfig().setRepositoryDirectory(directory); @@ -97,5 +89,22 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase return handler; } + + @Test + public void getDirectory() { + HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, + new DefaultFileSystem(), provider); + + HgConfig hgConfig = new HgConfig(); + hgConfig.setRepositoryDirectory(new File("/path")); + hgConfig.setHgBinary("hg"); + hgConfig.setPythonBinary("python"); + repositoryHandler.setConfig(hgConfig); + + Repository repository = new Repository("id", "git", "Name"); + + File path = repositoryHandler.getDirectory(repository); + assertEquals("/path/id", path.getAbsolutePath()); + assertTrue(path.getAbsolutePath().endsWith("id")); + } } -//~--- non-JDK imports -------------------------------------------------------- diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java index af80e27bcf..a0774c9596 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java @@ -1,19 +1,19 @@ /** * 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. + * 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. + * 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. - * + * 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 @@ -24,42 +24,56 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + *

* http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.repository; -//~--- non-JDK imports -------------------------------------------------------- +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.io.DefaultFileSystem; - -import static org.junit.Assert.*; - -//~--- JDK imports ------------------------------------------------------------ +import sonia.scm.repository.api.HookContextFactory; +import sonia.scm.repository.spi.HookEventFacade; +import sonia.scm.store.ConfigurationStore; +import sonia.scm.store.ConfigurationStoreFactory; import java.io.File; -import sonia.scm.store.ConfigurationStoreFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +//~--- JDK imports ------------------------------------------------------------ /** * * @author Sebastian Sdorra */ -public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase -{ +@RunWith(MockitoJUnitRunner.class) +public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { + + @Mock + private ConfigurationStoreFactory factory; + + @Mock + private ConfigurationStore store; + + @Mock + private com.google.inject.Provider repositoryManagerProvider; + + private HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); + + private HookEventFacade facade = new HookEventFacade(repositoryManagerProvider, hookContextFactory); - /** - * Method description - * - * - * @param directory - */ @Override - protected void checkDirectory(File directory) - { + protected void checkDirectory(File directory) { File format = new File(directory, "format"); assertTrue(format.exists()); @@ -71,21 +85,11 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase assertTrue(db.isDirectory()); } - /** - * Method description - * - * - * @param factory - * @param directory - * - * @return - */ @Override protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, - File directory) - { + File directory) { SvnRepositoryHandler handler = new SvnRepositoryHandler(factory, - new DefaultFileSystem(), null); + new DefaultFileSystem(), null); handler.init(contextProvider); @@ -98,4 +102,21 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase return handler; } + + @Test + public void getDirectory() { + when(factory.getStore(any(), any())).thenReturn(store); + SvnRepositoryHandler repositoryHandler = new SvnRepositoryHandler(factory, + new DefaultFileSystem(), facade); + + SvnConfig svnConfig = new SvnConfig(); + svnConfig.setRepositoryDirectory(new File("/path")); + repositoryHandler.setConfig(svnConfig); + + Repository repository = new Repository("id", "svn", "Name"); + + File path = repositoryHandler.getDirectory(repository); + assertEquals("/path/id", path.getAbsolutePath()); + assertTrue(path.getAbsolutePath().endsWith("id")); + } } diff --git a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java index 0a85231da0..fa2cc1b0fd 100644 --- a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java +++ b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java @@ -1,19 +1,19 @@ /** * 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. + * 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. + * 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. - * + * 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 @@ -24,113 +24,67 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + *

* http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- import sonia.scm.Type; import sonia.scm.io.DefaultFileSystem; - -//~--- JDK imports ------------------------------------------------------------ +import sonia.scm.store.ConfigurationStoreFactory; import java.io.File; -import java.io.IOException; -import sonia.scm.store.ConfigurationStoreFactory; +import java.util.HashSet; +import java.util.Set; + +//~--- JDK imports ------------------------------------------------------------ /** * * @author Sebastian Sdorra */ public class DummyRepositoryHandler - extends AbstractSimpleRepositoryHandler -{ + extends AbstractSimpleRepositoryHandler { - /** Field description */ public static final String TYPE_DISPLAYNAME = "Dummy"; - /** Field description */ public static final String TYPE_NAME = "dummy"; - /** Field description */ public static final Type TYPE = new Type(TYPE_NAME, TYPE_DISPLAYNAME); - //~--- constructors --------------------------------------------------------- + private Set existingRepoNames = new HashSet<>(); - /** - * Constructs ... - * - * - * @param storeFactory - */ - public DummyRepositoryHandler(ConfigurationStoreFactory storeFactory) - { + public DummyRepositoryHandler(ConfigurationStoreFactory storeFactory) { super(storeFactory, new DefaultFileSystem()); } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ @Override - public Type getType() - { + public Type getType() { return TYPE; } - //~--- methods -------------------------------------------------------------- - /** - * Method description - * - * - * @param repository - * @param directory - * - * @throws IOException - * @throws RepositoryException - */ @Override protected void create(Repository repository, File directory) - throws RepositoryException, IOException - { - - // do nothing + throws RepositoryException { + if (existingRepoNames.contains(repository.getNamespace() + repository.getName())) { + throw new RepositoryAlreadyExistsException("Repo exists"); + } else { + existingRepoNames.add(repository.getNamespace() + repository.getName()); + } } - /** - * Method description - * - * - * @return - */ @Override - protected SimpleRepositoryConfig createInitialConfig() - { + protected SimpleRepositoryConfig createInitialConfig() { return new SimpleRepositoryConfig(); } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ @Override - protected Class getConfigClass() - { + protected Class getConfigClass() { return SimpleRepositoryConfig.class; } } diff --git a/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java b/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java index d5d443fe31..c9ea3c7886 100644 --- a/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java +++ b/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java @@ -141,7 +141,7 @@ public final class RepositoryTestData heartOfGold.setName("HeartOfGold"); heartOfGold.setDescription( "Heart of Gold is the first prototype ship to successfully utilise the revolutionary Infinite Improbability Drive"); - + heartOfGold.setId("hogId"); return heartOfGold; } diff --git a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java index 19af367a1d..07a9495588 100644 --- a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java +++ b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java @@ -1,19 +1,19 @@ /** * 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. + * 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. + * 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. - * + * 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 @@ -24,169 +24,94 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + *

* http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- import org.junit.Test; - import sonia.scm.AbstractTestBase; +import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.InMemoryConfigurationStoreFactory; import sonia.scm.util.IOUtil; +import java.io.File; +import java.io.IOException; + import static org.junit.Assert.*; //~--- JDK imports ------------------------------------------------------------ -import java.io.File; -import java.io.IOException; -import sonia.scm.store.ConfigurationStoreFactory; - /** * * @author Sebastian Sdorra */ -public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase -{ +public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { + - /** - * Method description - * - * - * @param directory - */ protected abstract void checkDirectory(File directory); - /** - * Method description - * - * - * @param factory - * @param directory - * - * @return - */ protected abstract RepositoryHandler createRepositoryHandler( - ConfigurationStoreFactory factory, File directory); + ConfigurationStoreFactory factory, File directory); - /** - * Method description - * - * - * @throws IOException - * @throws RepositoryException - */ @Test - public void testCreate() throws RepositoryException, IOException - { + public void testCreate() throws RepositoryException, IOException { createRepository(); } - /** - * Method description - * - * - * @throws IOException - * @throws RepositoryException - */ @Test(expected = RepositoryAlreadyExistsException.class) public void testCreateExisitingRepository() - throws RepositoryException, IOException - { + throws RepositoryException, IOException { createRepository(); createRepository(); } - /** - * Method description - * - * - * @throws IOException - * @throws RepositoryException - */ @Test - public void testCreateResourcePath() throws RepositoryException, IOException - { + public void testCreateResourcePath() throws RepositoryException, IOException { Repository repository = createRepository(); String path = handler.createResourcePath(repository); assertNotNull(path); assertTrue(path.trim().length() > 0); - assertTrue(path.contains(repository.getName())); + assertTrue(path.contains(repository.getId())); } - /** - * Method description - * - * - * @throws IOException - * @throws RepositoryException - */ @Test - public void testDelete() throws RepositoryException, IOException - { + public void testDelete() throws RepositoryException, IOException { Repository repository = createRepository(); handler.delete(repository); - File directory = new File(baseDirectory, repository.getName()); + File directory = new File(baseDirectory, repository.getId()); assertFalse(directory.exists()); } - /** - * Method description - * - * - * @throws Exception - */ @Override - protected void postSetUp() throws Exception - { + protected void postSetUp() throws Exception { InMemoryConfigurationStoreFactory storeFactory = new InMemoryConfigurationStoreFactory(); baseDirectory = new File(contextProvider.getBaseDirectory(), "repositories"); IOUtil.mkdirs(baseDirectory); handler = createRepositoryHandler(storeFactory, baseDirectory); } - /** - * Method description - * - * - * @throws Exception - */ @Override - protected void preTearDown() throws Exception - { - if (handler != null) - { + protected void preTearDown() throws Exception { + if (handler != null) { handler.close(); } } - /** - * Method description - * - * - * @return - * - * @throws IOException - * @throws RepositoryException - */ - private Repository createRepository() throws RepositoryException, IOException - { + private Repository createRepository() throws RepositoryException, IOException { Repository repository = RepositoryTestData.createHeartOfGold(); handler.create(repository); - File directory = new File(baseDirectory, repository.getName()); + File directory = new File(baseDirectory, repository.getId()); assertTrue(directory.exists()); assertTrue(directory.isDirectory()); @@ -195,11 +120,8 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase return repository; } - //~--- fields --------------------------------------------------------------- - /** Field description */ protected File baseDirectory; - /** Field description */ private RepositoryHandler handler; } From 091af43daa0e5f34b3134c34405fe9b3dd6119a0 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Fri, 6 Jul 2018 12:16:51 +0200 Subject: [PATCH 014/186] Fixed merge errors --- .../AbstractSimpleRepositoryHandler.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index 0e0e05f74b..90015f0f31 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -81,8 +81,7 @@ public abstract class AbstractSimpleRepositoryHandler Date: Fri, 6 Jul 2018 13:49:42 +0200 Subject: [PATCH 015/186] Remove unused code in GroupRootResourceTest --- .../sonia/scm/api/v2/resources/GroupRootResourceTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java index 7f88ceb50f..9c943efdfa 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java @@ -115,8 +115,6 @@ public class GroupRootResourceTest { Group group = createDummyGroup(); when(groupManager.get("admin")).thenReturn(group); - Group updatedGroup = createDummyGroup(); - updatedGroup.setDescription("Updated description"); MockHttpRequest request = MockHttpRequest .put("/" + GroupRootResource.GROUPS_PATH_V2 + "admin") @@ -138,9 +136,6 @@ public class GroupRootResourceTest { URL url = Resources.getResource("sonia/scm/api/v2/group-test-update.json"); byte[] groupJson = Resources.toByteArray(url); - Group updatedGroup = createDummyGroup(); - updatedGroup.setDescription("Updated description"); - MockHttpRequest request = MockHttpRequest .put("/" + GroupRootResource.GROUPS_PATH_V2 + "idontexist") .contentType(VndMediaType.GROUP) From 8c68a2de2403279a1d6e94d19f2f2b984e1659e2 Mon Sep 17 00:00:00 2001 From: Michael Behlendorf Date: Fri, 6 Jul 2018 14:45:00 +0200 Subject: [PATCH 016/186] Implement global config endpoint v2 --- .../scm/api/v2/resources/GlobalConfigDto.java | 58 +++++++ ...obalConfigDtoToScmConfigurationMapper.java | 12 ++ .../v2/resources/GlobalConfigResource.java | 91 +++++++++++ .../scm/api/v2/resources/MapperModule.java | 3 + .../scm/api/v2/resources/ResourceLinks.java | 22 ++- ...mConfigurationToGlobalConfigDtoMapper.java | 36 +++++ ...ConfigDtoToScmConfigurationMapperTest.java | 33 ++++ .../resources/GlobalConfigResourceTest.java | 148 ++++++++++++++++++ .../api/v2/resources/ResourceLinksMock.java | 4 + .../api/v2/resources/ResourceLinksTest.java | 12 ++ ...figurationToGlobalConfigDtoMapperTest.java | 72 +++++++++ .../scm/api/v2/globalConfig-test-update.json | 3 + 12 files changed, 493 insertions(+), 1 deletion(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDto.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapper.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapperTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigResourceTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapperTest.java create mode 100644 scm-webapp/src/test/resources/sonia/scm/api/v2/globalConfig-test-update.json diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDto.java new file mode 100644 index 0000000000..928daf5c3a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDto.java @@ -0,0 +1,58 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import sonia.scm.xml.XmlSetStringAdapter; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.Set; + +@NoArgsConstructor +@Getter +@Setter +public class GlobalConfigDto extends HalRepresentation { + + private String proxyPassword; + private int proxyPort; + private String proxyServer; + private String proxyUser; + private boolean enableProxy; + private String realmDescription; + private boolean enableRepositoryArchive; + private boolean disableGroupingGrid; + private String dateFormat; + private boolean anonymousAccessEnabled; + @XmlElement(name = "admin-groups") + @XmlJavaTypeAdapter(XmlSetStringAdapter.class) + private Set adminGroups; + @XmlElement(name = "admin-users") + @XmlJavaTypeAdapter(XmlSetStringAdapter.class) + private Set adminUsers; + @XmlElement(name = "base-url") + private String baseUrl; + @XmlElement(name = "force-base-url") + private boolean forceBaseUrl; + @XmlElement(name = "login-attempt-limit") + private int loginAttemptLimit; + @XmlElement(name = "proxy-excludes") + @XmlJavaTypeAdapter(XmlSetStringAdapter.class) + private Set proxyExcludes; + @XmlElement(name = "skip-failed-authenticators") + private boolean skipFailedAuthenticators; + @XmlElement(name = "plugin-url") + private String pluginUrl; + @XmlElement(name = "login-attempt-limit-timeout") + private long loginAttemptLimitTimeout; + @XmlElement(name = "xsrf-protection") + private boolean enabledXsrfProtection; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapper.java new file mode 100644 index 0000000000..3746f00e08 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapper.java @@ -0,0 +1,12 @@ +package sonia.scm.api.v2.resources; + +import org.mapstruct.Mapper; +import sonia.scm.config.ScmConfiguration; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class GlobalConfigDtoToScmConfigurationMapper { + + public abstract ScmConfiguration map(GlobalConfigDto dto); +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigResource.java new file mode 100644 index 0000000000..1e1698a45a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GlobalConfigResource.java @@ -0,0 +1,91 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import org.apache.shiro.SecurityUtils; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.Role; +import sonia.scm.util.ScmConfigurationUtil; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +@Path(GlobalConfigResource.GLOBAL_CONFIG_PATH_V2) +public class GlobalConfigResource { + + static final String GLOBAL_CONFIG_PATH_V2 = "v2/config/global"; + private final GlobalConfigDtoToScmConfigurationMapper dtoToConfigMapper; + private final ScmConfigurationToGlobalConfigDtoMapper configToDtoMapper; + private final ScmConfiguration configuration; + + @Inject + public GlobalConfigResource(GlobalConfigDtoToScmConfigurationMapper dtoToConfigMapper, ScmConfigurationToGlobalConfigDtoMapper configToDtoMapper, ScmConfiguration configuration) { + this.dtoToConfigMapper = dtoToConfigMapper; + this.configToDtoMapper = configToDtoMapper; + this.configuration = configuration; + } + + /** + * Returns the global scm config. + */ + @GET + @Path("") + @Produces(VndMediaType.GLOBAL_CONFIG) + @TypeHint(UserDto.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the global config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response get() { + Response response; + + // TODO ConfigPermisions? + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + response = Response.ok(configToDtoMapper.map(configuration)).build(); + } else { + response = Response.status(Response.Status.FORBIDDEN).build(); + } + + return response; + } + + /** + * Modifies the global scm config. + * + * @param configDto new global scm configuration as DTO + */ + @PUT + @Path("") + @Consumes(VndMediaType.GLOBAL_CONFIG) + @StatusCodes({ + @ResponseCode(code = 201, condition = "update success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the global config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response update(GlobalConfigDto configDto, @Context UriInfo uriInfo) { + Response response; + + // TODO ConfigPermisions? + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + ScmConfiguration config = dtoToConfigMapper.map(configDto); + configuration.load(config); + synchronized (ScmConfiguration.class) { + ScmConfigurationUtil.getInstance().store(configuration); + } + response = Response.created(uriInfo.getRequestUri()).build(); + } else { + response = Response.status(Response.Status.FORBIDDEN).build(); + } + + return response; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index fec2a677b5..72a4e5e062 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -15,6 +15,9 @@ public class MapperModule extends AbstractModule { bind(GroupToGroupDtoMapper.class).to(Mappers.getMapper(GroupToGroupDtoMapper.class).getClass()); bind(GroupCollectionToDtoMapper.class); + bind(ScmConfigurationToGlobalConfigDtoMapper.class).to(Mappers.getMapper(ScmConfigurationToGlobalConfigDtoMapper.class).getClass()); + bind(GlobalConfigDtoToScmConfigurationMapper.class).to(Mappers.getMapper(GlobalConfigDtoToScmConfigurationMapper.class).getClass()); + bind(UriInfoStore.class).in(ServletScopes.REQUEST); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index 13691da0f6..83faa57e35 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -76,7 +76,7 @@ class ResourceLinks { return userLinkBuilder.method("getUserResource").parameters(name).method("delete").parameters().href(); } - String update(String name) { + String update(String name) { return userLinkBuilder.method("getUserResource").parameters(name).method("update").parameters().href(); } } @@ -100,4 +100,24 @@ class ResourceLinks { return collectionLinkBuilder.method("getUserCollectionResource").parameters().method("create").parameters().href(); } } + + GlobalConfigLinks globalConfig() { + return new GlobalConfigLinks(uriInfoStore.get()); + } + + static class GlobalConfigLinks { + private final LinkBuilder globalConfigLinkBuilder; + + private GlobalConfigLinks(UriInfo uriInfo) { + globalConfigLinkBuilder = new LinkBuilder(uriInfo, GlobalConfigResource.class); + } + + String self() { + return globalConfigLinkBuilder.method("get").parameters().href(); + } + + String update() { + return globalConfigLinkBuilder.method("update").parameters().href(); + } + } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapper.java new file mode 100644 index 0000000000..129ebc7fbe --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapper.java @@ -0,0 +1,36 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import org.apache.shiro.SecurityUtils; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.Role; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class ScmConfigurationToGlobalConfigDtoMapper { + + @Inject + private ResourceLinks resourceLinks; + + public abstract GlobalConfigDto map(ScmConfiguration config); + + @AfterMapping + void appendLinks(ScmConfiguration config, @MappingTarget GlobalConfigDto target) { + Links.Builder linksBuilder = linkingTo().self(resourceLinks.globalConfig().self()); + // TODO: ConfigPermissions? + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { + linksBuilder.single(link("update", resourceLinks.globalConfig().update())); + } + target.add(linksBuilder.build()); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapperTest.java new file mode 100644 index 0000000000..b08303ca4e --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigDtoToScmConfigurationMapperTest.java @@ -0,0 +1,33 @@ +package sonia.scm.api.v2.resources; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import sonia.scm.config.ScmConfiguration; + +import static org.junit.Assert.assertEquals; +import static org.mockito.MockitoAnnotations.initMocks; + +public class GlobalConfigDtoToScmConfigurationMapperTest { + + @InjectMocks + private GlobalConfigDtoToScmConfigurationMapperImpl mapper; + + @Test + public void shouldMapFields() { + GlobalConfigDto dto = createDefaultDto(); + ScmConfiguration config = mapper.map(dto); + assertEquals("baseurl" , config.getBaseUrl()); + } + + @Before + public void init() { + initMocks(this); + } + + private GlobalConfigDto createDefaultDto() { + GlobalConfigDto globalConfigDto = new GlobalConfigDto(); + globalConfigDto.setBaseUrl("baseurl"); + return globalConfigDto; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigResourceTest.java new file mode 100644 index 0000000000..49945bedd2 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GlobalConfigResourceTest.java @@ -0,0 +1,148 @@ +package sonia.scm.api.v2.resources; + +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import com.google.common.io.Resources; +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.web.VndMediaType; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashSet; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.MockitoAnnotations.initMocks; + +@SubjectAware( + username = "trillian", + password = "secret", + configuration = "classpath:sonia/scm/repository/shiro.ini" +) +public class GlobalConfigResourceTest { + + @Rule + public ShiroRule shiro = new ShiroRule(); + + private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ResourceLinks resourceLinks; + + @InjectMocks + private GlobalConfigDtoToScmConfigurationMapperImpl dtoToConfigMapper; + @InjectMocks + private ScmConfigurationToGlobalConfigDtoMapperImpl configToDtoMapper; + + @Before + public void prepareEnvironment() throws IOException { + initMocks(this); + + ResourceLinksMock.initMock(resourceLinks, URI.create("/")); + + GlobalConfigResource globalConfigResource = new GlobalConfigResource(dtoToConfigMapper, + configToDtoMapper, createConfiguration()); + + dispatcher.getRegistry().addSingletonResource(globalConfigResource); + } + + @Test + public void shouldGetGlobalConfig() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"heartOfGold\"")); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config/global")); + assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/global")); + } + + @SubjectAware( + username = "dent" + ) + @Test + public void shouldGetForbiddenGlobalConfig() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); + } + + @Test + public void shouldUpdateGlobalConfig() throws URISyntaxException, IOException { + URL url = Resources.getResource("sonia/scm/api/v2/globalConfig-test-update.json"); + byte[] configJson = Resources.toByteArray(url); + MockHttpRequest request = MockHttpRequest.put("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2) + .contentType(VndMediaType.GLOBAL_CONFIG) + .content(configJson); + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_CREATED, response.getStatus()); + + request = MockHttpRequest.get("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); + response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"newPassword\"")); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config/global")); + assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config/global")); + + } + + @SubjectAware( + username = "dent" + ) + @Test + public void shouldUpdateForbiddenGlobalConfig() throws URISyntaxException, IOException { + URL url = Resources.getResource("sonia/scm/api/v2/globalConfig-test-update.json"); + byte[] configJson = Resources.toByteArray(url); + MockHttpRequest request = MockHttpRequest.put("/" + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2) + .contentType(VndMediaType.GLOBAL_CONFIG) + .content(configJson); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); + } + + public static ScmConfiguration createConfiguration() { + ScmConfiguration scmConfiguration = new ScmConfiguration(); + scmConfiguration.setProxyPassword("heartOfGold"); + scmConfiguration.setProxyPort(1234); + scmConfiguration.setProxyServer("proxyserver"); + scmConfiguration.setProxyUser("trillian"); + scmConfiguration.setEnableProxy(true); + scmConfiguration.setRealmDescription("description"); + scmConfiguration.setEnableRepositoryArchive(true); + scmConfiguration.setDisableGroupingGrid(true); + scmConfiguration.setDateFormat("dd"); + scmConfiguration.setAnonymousAccessEnabled(true); + scmConfiguration.setAdminGroups(new HashSet<>(Arrays.asList("group"))); + scmConfiguration.setAdminUsers(new HashSet<>(Arrays.asList("user1"))); + scmConfiguration.setBaseUrl("baseurl"); + scmConfiguration.setForceBaseUrl(true); + scmConfiguration.setLoginAttemptLimit(1); + scmConfiguration.setProxyExcludes(new HashSet<>(Arrays.asList("arthur", "dent"))); + scmConfiguration.setSkipFailedAuthenticators(true); + scmConfiguration.setPluginUrl("pluginurl"); + scmConfiguration.setLoginAttemptLimitTimeout(2); + scmConfiguration.setEnabledXsrfProtection(true); + return scmConfiguration; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index 119ced5397..dc1c277984 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -4,6 +4,7 @@ import java.net.URI; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.when; +import static sonia.scm.api.v2.resources.GlobalConfigResource.GLOBAL_CONFIG_PATH_V2; import static sonia.scm.api.v2.resources.GroupRootResource.GROUPS_PATH_V2; import static sonia.scm.api.v2.resources.UserRootResource.USERS_PATH_V2; @@ -22,5 +23,8 @@ public class ResourceLinksMock { when(resourceLinks.groupCollection().self()).thenAnswer(invocation -> baseUri + GROUPS_PATH_V2); when(resourceLinks.groupCollection().create()).thenAnswer(invocation -> baseUri + GROUPS_PATH_V2); + + when(resourceLinks.globalConfig().self()).thenAnswer(invocation -> baseUri + GLOBAL_CONFIG_PATH_V2); + when(resourceLinks.globalConfig().update()).thenAnswer(invocation -> baseUri + GLOBAL_CONFIG_PATH_V2); } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java index 103eebd411..53a8578496 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java @@ -84,6 +84,18 @@ public class ResourceLinksTest { assertEquals(BASE_URL + GroupRootResource.GROUPS_PATH_V2, url); } + @Test + public void shouldCreateCorrectGlobalConfigSelfUrl() { + String url = resourceLinks.globalConfig().self(); + assertEquals(BASE_URL + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2, url); + } + + @Test + public void shouldCreateCorrectGlobalConfigUpdateUrl() { + String url = resourceLinks.globalConfig().update(); + assertEquals(BASE_URL + GlobalConfigResource.GLOBAL_CONFIG_PATH_V2, url); + } + @Before public void initUriInfo() { initMocks(this); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapperTest.java new file mode 100644 index 0000000000..e67e8764d5 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToGlobalConfigDtoMapperTest.java @@ -0,0 +1,72 @@ +package sonia.scm.api.v2.resources; + +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.support.SubjectThreadState; +import org.apache.shiro.util.ThreadContext; +import org.apache.shiro.util.ThreadState; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.Role; + +import java.net.URI; +import java.net.URISyntaxException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; +import static sonia.scm.api.v2.resources.GlobalConfigResourceTest.createConfiguration; + +public class ScmConfigurationToGlobalConfigDtoMapperTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ResourceLinks resourceLinks; + + @InjectMocks + private ScmConfigurationToGlobalConfigDtoMapperImpl mapper; + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + private URI expectedBaseUri; + + @Before + public void init() throws URISyntaxException { + initMocks(this); + URI baseUri = new URI("http://example.com/base/"); + expectedBaseUri = baseUri.resolve(GlobalConfigResource.GLOBAL_CONFIG_PATH_V2); + subjectThreadState.bind(); + ResourceLinksMock.initMock(resourceLinks, baseUri); + ThreadContext.bind(subject); + } + + @Test + public void shouldMapFields() { + ScmConfiguration config = createConfiguration(); + + when(subject.hasRole(Role.ADMIN)).thenReturn(true); + GlobalConfigDto dto = mapper.map(config); + + assertEquals("baseurl", dto.getBaseUrl()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); + } + + @Test + public void shouldMapFieldsWithoutUpdate() { + ScmConfiguration config = createConfiguration(); + + when(subject.hasRole(Role.ADMIN)).thenReturn(false); + GlobalConfigDto dto = mapper.map(config); + + assertEquals("baseurl", dto.getBaseUrl()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertFalse(dto.getLinks().hasLink("update")); + } + +} diff --git a/scm-webapp/src/test/resources/sonia/scm/api/v2/globalConfig-test-update.json b/scm-webapp/src/test/resources/sonia/scm/api/v2/globalConfig-test-update.json new file mode 100644 index 0000000000..bc45315a0c --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/api/v2/globalConfig-test-update.json @@ -0,0 +1,3 @@ +{ + "proxyPassword": "newPassword" +} From 6f34f094f1268603ff63179e44d545ba4ebda75d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Fri, 6 Jul 2018 15:03:13 +0200 Subject: [PATCH 017/186] Create id for all test repositories --- .../scm/repository/RepositoryTestData.java | 152 +++++++----------- 1 file changed, 54 insertions(+), 98 deletions(-) diff --git a/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java b/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java index c9ea3c7886..65fad6f80d 100644 --- a/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java +++ b/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java @@ -33,147 +33,103 @@ package sonia.scm.repository; -/** - * - * @author Sebastian Sdorra - */ public final class RepositoryTestData { - /** - * Constructs ... - * - */ private RepositoryTestData() {} - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ public static Repository create42Puzzle() { return create42Puzzle(DummyRepositoryHandler.TYPE_NAME); } - /** - * Method description - * - * - * @param type - * - * @return - */ public static Repository create42Puzzle(String type) { - Repository repository = new Repository(); - - repository.setType(type); - repository.setContact("douglas.adams@hitchhiker.com"); - repository.setName("42Puzzle"); - repository.setDescription("The 42 Puzzle"); - - return repository; + return new Builder() + .type(type) + .contact("douglas.adams@hitchhiker.com") + .name("42Puzzle") + .description("The 42 Puzzle") + .build(); } - /** - * Method description - * - * - * @return - */ public static Repository createHappyVerticalPeopleTransporter() { return createHappyVerticalPeopleTransporter( DummyRepositoryHandler.TYPE_NAME); } - /** - * Method description - * - * - * - * @param type - * @return - */ public static Repository createHappyVerticalPeopleTransporter(String type) { - Repository happyVerticalPeopleTransporter = new Repository(); - - happyVerticalPeopleTransporter.setType(type); - happyVerticalPeopleTransporter.setContact( - "zaphod.beeblebrox@hitchhiker.com"); - happyVerticalPeopleTransporter.setName("happyVerticalPeopleTransporter"); - happyVerticalPeopleTransporter.setDescription( - "Happy Vertical People Transporter"); - - return happyVerticalPeopleTransporter; + return new Builder() + .type(type) + .contact("zaphod.beeblebrox@hitchhiker.com") + .name("happyVerticalPeopleTransporter") + .description("Happy Vertical People Transporter") + .build(); } - /** - * Method description - * - * - * @return - */ public static Repository createHeartOfGold() { return createHeartOfGold(DummyRepositoryHandler.TYPE_NAME); } - /** - * Method description - * - * - * - * @param type - * @return - */ public static Repository createHeartOfGold(String type) { - Repository heartOfGold = new Repository(); - - heartOfGold.setType(type); - heartOfGold.setContact("zaphod.beeblebrox@hitchhiker.com"); - heartOfGold.setName("HeartOfGold"); - heartOfGold.setDescription( - "Heart of Gold is the first prototype ship to successfully utilise the revolutionary Infinite Improbability Drive"); - heartOfGold.setId("hogId"); - return heartOfGold; + return new Builder() + .type(type) + .contact("zaphod.beeblebrox@hitchhiker.com") + .name("HeartOfGold") + .description( + "Heart of Gold is the first prototype ship to successfully utilise the revolutionary Infinite Improbability Drive") + .build(); } - /** - * Method description - * - * - * @return - */ public static Repository createRestaurantAtTheEndOfTheUniverse() { return createRestaurantAtTheEndOfTheUniverse( DummyRepositoryHandler.TYPE_NAME); } - /** - * Method description - * - * - * @param type - * - * @return - */ public static Repository createRestaurantAtTheEndOfTheUniverse(String type) { + return new Builder() + .type(type) + .contact("douglas.adams@hitchhiker.com") + .name("RestaurantAtTheEndOfTheUniverse") + .description("The Restaurant at the End of the Universe") + .build(); + } + + private static class Builder { + private static int nextID = 0; Repository repository = new Repository(); + { + repository.setId("ID-" + ++nextID); + } - repository.setType(type); - repository.setContact("douglas.adams@hitchhiker.com"); - repository.setName("RestaurantAtTheEndOfTheUniverse"); - repository.setDescription("The Restaurant at the End of the Universe"); + Builder type(String type) { + repository.setType(type); + return this; + } - return repository; + Builder contact(String contact) { + repository.setContact(contact); + return this; + } + + Builder name(String name) { + repository.setName(name); + return this; + } + + Builder description(String description) { + repository.setDescription(description); + return this; + } + + public Repository build() { + return repository; + } } } From 43ca72255e071f3e7d5e59d5cdf182fc1c0f6171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 9 Jul 2018 10:47:28 +0200 Subject: [PATCH 018/186] Remove redundant checks --- .../test/java/sonia/scm/repository/GitRepositoryHandlerTest.java | 1 - .../test/java/sonia/scm/repository/HgRepositoryHandlerTest.java | 1 - .../test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java | 1 - 3 files changed, 3 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java index 741892a0b1..d5201dca83 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java @@ -110,6 +110,5 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { File path = repositoryHandler.getDirectory(repository); assertEquals("/path/id", path.getAbsolutePath()); - assertTrue(path.getAbsolutePath().endsWith("id")); } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java index 2fdc039c53..c5d62dc054 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java @@ -105,6 +105,5 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { File path = repositoryHandler.getDirectory(repository); assertEquals("/path/id", path.getAbsolutePath()); - assertTrue(path.getAbsolutePath().endsWith("id")); } } diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java index a0774c9596..c7cd78dc75 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java @@ -117,6 +117,5 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { File path = repositoryHandler.getDirectory(repository); assertEquals("/path/id", path.getAbsolutePath()); - assertTrue(path.getAbsolutePath().endsWith("id")); } } From 643e6693b6fa626e4c14416bccfaa4e19113f85e Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Mon, 9 Jul 2018 11:38:13 +0200 Subject: [PATCH 019/186] Implemented login & added tests --- .../main/java/sonia/scm/web/VndMediaType.java | 1 + scm-ui/package.json | 14 +++- scm-ui/src/containers/App.js | 29 ++++++- scm-ui/src/containers/Login.js | 4 - scm-ui/src/modules/login.js | 79 +++++++++++++++++-- scm-ui/src/modules/login.test.js | 49 ++++++++++++ scm-ui/src/users/modules/users.js | 17 ++-- .../scm/api/v2/resources/MeResource.java | 34 ++++++++ .../java/sonia/scm/filter/SecurityFilter.java | 35 +------- 9 files changed, 204 insertions(+), 58 deletions(-) create mode 100644 scm-ui/src/modules/login.test.js create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 3ec121f9a4..c30a03c004 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -16,6 +16,7 @@ public class VndMediaType { public static final String GROUP = PREFIX + "group" + SUFFIX; public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX; public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX; + public static final String ME = PREFIX + "me" + SUFFIX; private VndMediaType() { } diff --git a/scm-ui/package.json b/scm-ui/package.json index 8513981bf5..145839607c 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -1,4 +1,5 @@ { + "homepage": "/scm", "name": "scm-ui", "version": "0.1.0", "private": true, @@ -21,12 +22,21 @@ "scripts": { "start": "react-scripts start", "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", + "test": "yarn flow && jest", "eject": "react-scripts eject", "flow": "flow" }, - "proxy": "http://localhost:8081/scm", + "proxy": { + "/scm/api": { + "target": "http://localhost:8081" + } + }, "devDependencies": { "prettier": "^1.13.7" + }, + "babel": { + "presets": [ + "react-app" + ] } } diff --git a/scm-ui/src/containers/App.js b/scm-ui/src/containers/App.js index b30ca75d0d..1ee8df1c98 100644 --- a/scm-ui/src/containers/App.js +++ b/scm-ui/src/containers/App.js @@ -2,15 +2,22 @@ import React, { Component } from "react"; import Navigation from "./Navigation"; import Main from "./Main"; import Login from "./Login"; +import { getIsAuthenticated } from "../modules/login"; +import { connect } from "react-redux"; import { withRouter } from "react-router-dom"; type Props = { - login: boolean + login: boolean, + username: string, + getAuthState: any }; class App extends Component { + componentWillMount() { + this.props.getAuthState(); + } render() { - const { login } = this.props; + const { login, username } = this.props.login; if (!login) { return ( @@ -21,6 +28,7 @@ class App extends Component { } else { return (

+

Welcome, {username}!

@@ -29,4 +37,19 @@ class App extends Component { } } -export default withRouter(App); +const mapDispatchToProps = dispatch => { + return { + getAuthState: () => dispatch(getIsAuthenticated()) + }; +}; + +const mapStateToProps = state => { + return { login: state.login }; +}; + +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps + )(App) +); diff --git a/scm-ui/src/containers/Login.js b/scm-ui/src/containers/Login.js index fb20d48366..e811927831 100644 --- a/scm-ui/src/containers/Login.js +++ b/scm-ui/src/containers/Login.js @@ -74,7 +74,3 @@ const StyledLogin = injectSheet(styles)( )(Login) ); export default StyledLogin; -// export default connect( -// mapStateToProps, -// mapDispatchToProps -// )(StyledLogin); diff --git a/scm-ui/src/modules/login.js b/scm-ui/src/modules/login.js index e46abc44f4..53e2049ec8 100644 --- a/scm-ui/src/modules/login.js +++ b/scm-ui/src/modules/login.js @@ -1,9 +1,60 @@ //@flow -const LOGIN = "scm/auth/login"; -const LOGIN_REQUEST = "scm/auth/login_request"; -const LOGIN_SUCCESSFUL = "scm/auth/login_successful"; -const LOGIN_FAILED = "scm/auth/login_failed"; +const LOGIN_URL = "/scm/api/rest/v2/auth/access_token"; +const AUTHENTICATION_INFO_URL = "/scm/api/rest/v2/me"; + +export const LOGIN = "scm/auth/login"; +export const LOGIN_REQUEST = "scm/auth/login_request"; +export const LOGIN_SUCCESSFUL = "scm/auth/login_successful"; +export const LOGIN_FAILED = "scm/auth/login_failed"; +export const GET_IS_AUTHENTICATED_REQUEST = "scm/auth/is_authenticated_request"; +export const GET_IS_AUTHENTICATED = "scm/auth/get_is_authenticated"; +export const IS_AUTHENTICATED = "scm/auth/is_authenticated"; +export const IS_NOT_AUTHENTICATED = "scm/auth/is_not_authenticated"; + +export function getIsAuthenticatedRequest() { + return { + type: GET_IS_AUTHENTICATED_REQUEST + }; +} + +export function getIsAuthenticated() { + return function(dispatch) { + dispatch(getIsAuthenticatedRequest()); + + return fetch(AUTHENTICATION_INFO_URL, { + credentials: "same-origin", + headers: { + Cache: "no-cache" + } + }) + .then(response => { + if (response.ok) { + return response.json(); + } else { + dispatch(isNotAuthenticated()); + } + }) + .then(data => { + if (data) { + dispatch(isAuthenticated(data.username)); + } + }); + }; +} + +export function isAuthenticated(username: string) { + return { + type: IS_AUTHENTICATED, + username + }; +} + +export function isNotAuthenticated() { + return { + type: IS_NOT_AUTHENTICATED + }; +} export function loginRequest() { return { @@ -18,18 +69,19 @@ export function login(username: string, password: string) { password: username, username: password }; - console.log(login_data); return function(dispatch) { dispatch(loginRequest()); - return fetch("/api/rest/v2/auth/access_token", { + return fetch(LOGIN_URL, { method: "POST", headers: { "Content-Type": "application/json; charset=utf-8" }, + credentials: "same-origin", body: JSON.stringify(login_data) }).then( response => { if (response.ok) { + dispatch(getIsAuthenticated()); dispatch(loginSuccessful()); } }, @@ -44,7 +96,7 @@ export function loginSuccessful() { }; } -export default function reducer(state = {}, action = {}) { +export default function reducer(state: any = {}, action: any = {}) { switch (action.type) { case LOGIN: return { @@ -64,6 +116,19 @@ export default function reducer(state = {}, action = {}) { login: false, error: action.payload }; + case IS_AUTHENTICATED: + return { + ...state, + login: true, + username: action.username + }; + case IS_NOT_AUTHENTICATED: + return { + ...state, + login: false, + username: null, + error: null + }; default: return state; diff --git a/scm-ui/src/modules/login.test.js b/scm-ui/src/modules/login.test.js new file mode 100644 index 0000000000..dd009a9ee8 --- /dev/null +++ b/scm-ui/src/modules/login.test.js @@ -0,0 +1,49 @@ +// @flow +import reducer, { + LOGIN_REQUEST, + LOGIN_FAILED, + IS_AUTHENTICATED, + IS_NOT_AUTHENTICATED +} from "./login"; +import { LOGIN, LOGIN_SUCCESSFUL } from "./login"; + +test("login", () => { + var newState = reducer({}, { type: LOGIN }); + expect(newState.login).toBe(false); + expect(newState.error).toBe(null); +}); + +test("login request", () => { + var newState = reducer({}, { type: LOGIN_REQUEST }); + expect(newState.login).toBe(undefined); +}); + +test("login successful", () => { + var newState = reducer({ login: false }, { type: LOGIN_SUCCESSFUL }); + expect(newState.login).toBe(true); + expect(newState.error).toBe(null); +}); + +test("login failed", () => { + var newState = reducer({}, { type: LOGIN_FAILED, payload: "error!" }); + expect(newState.login).toBe(false); + expect(newState.error).toBe("error!"); +}); + +test("is authenticated", () => { + var newState = reducer( + { login: false }, + { type: IS_AUTHENTICATED, username: "test" } + ); + expect(newState.login).toBeTruthy(); + expect(newState.username).toBe("test"); +}); + +test("is not authenticated", () => { + var newState = reducer( + { login: true, username: "foo" }, + { type: IS_NOT_AUTHENTICATED } + ); + expect(newState.login).toBe(false); + expect(newState.username).toBeNull(); +}); diff --git a/scm-ui/src/users/modules/users.js b/scm-ui/src/users/modules/users.js index ed92f7ba10..c1103d60f3 100644 --- a/scm-ui/src/users/modules/users.js +++ b/scm-ui/src/users/modules/users.js @@ -1,6 +1,8 @@ -const FETCH_USERS = 'scm/users/FETCH'; -const FETCH_USERS_SUCCESS= 'scm/users/FETCH_SUCCESS'; -const FETCH_USERS_FAILURE = 'scm/users/FETCH_FAILURE'; +// @flow + +const FETCH_USERS = "scm/users/FETCH"; +const FETCH_USERS_SUCCESS = "scm/users/FETCH_SUCCESS"; +const FETCH_USERS_FAILURE = "scm/users/FETCH_FAILURE"; function requestUsers() { return { @@ -8,12 +10,11 @@ function requestUsers() { }; } - function fetchUsers() { return function(dispatch) { dispatch(requestUsers()); return null; - } + }; } export function shouldFetchUsers(state: any): boolean { @@ -26,10 +27,10 @@ export function fetchUsersIfNeeded() { if (shouldFetchUsers(getState())) { dispatch(fetchUsers()); } - } + }; } -export default function reducer(state = {}, action = {}) { +export default function reducer(state: any = {}, action: any = {}) { switch (action.type) { case FETCH_USERS: return { @@ -53,6 +54,6 @@ export default function reducer(state = {}, action = {}) { }; default: - return state + return state; } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java new file mode 100644 index 0000000000..5646ca60cf --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeResource.java @@ -0,0 +1,34 @@ +package sonia.scm.api.v2.resources; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.shiro.SecurityUtils; +import sonia.scm.web.VndMediaType; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +@Path(MeResource.ME_PATH_V2) +public class MeResource { + static final String ME_PATH_V2 = "v2/me/"; + + @GET + @Produces(VndMediaType.ME) + public Response get() { + MeDto meDto = new MeDto((String) SecurityUtils.getSubject().getPrincipals().getPrimaryPrincipal()); + return Response.ok(meDto).build(); + } + + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + class MeDto { + String username; + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java index 07475e5853..965e097f64 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java @@ -75,33 +75,14 @@ public class SecurityFilter extends HttpFilter public static final String URLV2_AUTHENTICATION = "/api/rest/v2/auth"; - //~--- constructors --------------------------------------------------------- + private final ScmConfiguration configuration; - /** - * Constructs ... - * - * - * @param configuration - */ @Inject public SecurityFilter(ScmConfiguration configuration) { this.configuration = configuration; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param request - * @param response - * @param chain - * - * @throws IOException - * @throws ServletException - */ @Override protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) @@ -139,16 +120,6 @@ public class SecurityFilter extends HttpFilter } } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param subject - * - * @return - */ protected boolean hasPermission(Subject subject) { return ((configuration != null) @@ -173,8 +144,4 @@ public class SecurityFilter extends HttpFilter return username; } - //~--- fields --------------------------------------------------------------- - - /** scm configuration */ - private final ScmConfiguration configuration; } From 3c3c2647b91bf327aaee012ee410fcc277dde107 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Mon, 9 Jul 2018 11:41:23 +0200 Subject: [PATCH 020/186] Removed flow from yarn test --- scm-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/package.json b/scm-ui/package.json index 145839607c..c91b237aa5 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -22,7 +22,7 @@ "scripts": { "start": "react-scripts start", "build": "react-scripts build", - "test": "yarn flow && jest", + "test": "jest", "eject": "react-scripts eject", "flow": "flow" }, From 06ca71ce8683d5a65ced7f8626abe926c13e2eca Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 10 Jul 2018 08:38:38 +0200 Subject: [PATCH 021/186] Added bootstrapping for user table --- scm-ui/src/users/containers/UserRow.js | 20 +++++++ scm-ui/src/users/containers/Users.js | 74 +++++++++++++++++--------- scm-ui/src/users/modules/users.js | 34 +++++++++--- 3 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 scm-ui/src/users/containers/UserRow.js diff --git a/scm-ui/src/users/containers/UserRow.js b/scm-ui/src/users/containers/UserRow.js new file mode 100644 index 0000000000..d9184d3586 --- /dev/null +++ b/scm-ui/src/users/containers/UserRow.js @@ -0,0 +1,20 @@ +// @flow +import React from "react"; + +type Props = { + user: any +}; + +export default class UserRow extends React.Component { + render() { + return ( + + {this.props.user.displayName} + {this.props.user.mail} + + + + + ); + } +} diff --git a/scm-ui/src/users/containers/Users.js b/scm-ui/src/users/containers/Users.js index e3f3c5190d..9a628c6fc2 100644 --- a/scm-ui/src/users/containers/Users.js +++ b/scm-ui/src/users/containers/Users.js @@ -1,48 +1,70 @@ // @flow -import React from 'react'; -import { connect } from 'react-redux'; +import React from "react"; +import { connect } from "react-redux"; -import { fetchUsersIfNeeded } from '../modules/users'; -import Login from '../../containers/Login'; +import { fetchUsersIfNeeded, fetchUsers } from "../modules/users"; +import Login from "../../containers/Login"; +import UserRow from "./UserRow"; type Props = { login: boolean, error: any, users: any, - fetchUsersIfNeeded: () => void -} + fetchUsersIfNeeded: () => void, + fetchUsers: () => void +}; class Users extends React.Component { - - componentDidMount() { - this.props.fetchUsersIfNeeded(); + componentWillMount() { + this.props.fetchUsers(); } render() { - const { login, error, users } = this.props; - - + if (this.props.users) { return (

SCM

Users

+ + + + + + + + + + {this.props.users.map((user, index) => { + return ; + })} + +
NameE-MailAdmin
); - - } - -} - -const mapStateToProps = (state) => { - return null; -}; - -const mapDispatchToProps = (dispatch) => { - return { - fetchUsersIfNeeded: () => { - dispatch(fetchUsersIfNeeded()) + } else { + return
Loading...
; } } +} + +const mapStateToProps = state => { + return { + users: state.users.users + }; }; -export default connect(mapStateToProps, mapDispatchToProps)(Users); +const mapDispatchToProps = dispatch => { + return { + fetchUsersIfNeeded: () => { + dispatch(fetchUsersIfNeeded()); + }, + fetchUsers: () => { + dispatch(fetchUsers()); + } + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Users); diff --git a/scm-ui/src/users/modules/users.js b/scm-ui/src/users/modules/users.js index c1103d60f3..192e13e0eb 100644 --- a/scm-ui/src/users/modules/users.js +++ b/scm-ui/src/users/modules/users.js @@ -4,16 +4,38 @@ const FETCH_USERS = "scm/users/FETCH"; const FETCH_USERS_SUCCESS = "scm/users/FETCH_SUCCESS"; const FETCH_USERS_FAILURE = "scm/users/FETCH_FAILURE"; +const USERS_URL = "/scm/api/rest/v2/users"; + function requestUsers() { return { type: FETCH_USERS }; } -function fetchUsers() { +export function fetchUsers() { return function(dispatch) { - dispatch(requestUsers()); - return null; + // dispatch(requestUsers()); + return fetch(USERS_URL, { + credentials: "same-origin", + headers: { + Cache: "no-cache" + } + }) + .then(response => { + if (response.ok) { + return response.json(); + } + }) + .then(data => { + dispatch(fetchUsersSuccess(data)); + }); + }; +} + +function fetchUsersSuccess(users: any) { + return { + type: FETCH_USERS_SUCCESS, + payload: users }; } @@ -35,16 +57,14 @@ export default function reducer(state: any = {}, action: any = {}) { case FETCH_USERS: return { ...state, - login: true, - error: null + users: [{ name: "" }] }; case FETCH_USERS_SUCCESS: return { ...state, - login: false, timestamp: action.timestamp, error: null, - users: action.payload + users: action.payload._embedded.users }; case FETCH_USERS_FAILURE: return { From 1e86353a823f9a6f80b7e7fa2d6f3f53aa3fea5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 10 Jul 2018 08:55:53 +0200 Subject: [PATCH 022/186] use link component instead of --- scm-ui/src/repositories/containers/Repositories.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repositories/containers/Repositories.js b/scm-ui/src/repositories/containers/Repositories.js index 5df04cbc4c..a7cd59e7de 100644 --- a/scm-ui/src/repositories/containers/Repositories.js +++ b/scm-ui/src/repositories/containers/Repositories.js @@ -3,7 +3,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { fetchRepositoriesIfNeeded } from '../modules/repositories'; -import Login from '../../containers/Login'; +import { Link } from 'react-router-dom' type Props = { @@ -27,9 +27,7 @@ class Repositories extends React.Component { ) From 0648586092165238dbee003fcd0ad2bb9a67f672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 10 Jul 2018 15:18:37 +0200 Subject: [PATCH 023/186] mockup delete button for user --- scm-ui/src/apiclient.js | 8 +- .../repositories/containers/Repositories.js | 2 +- .../src/users/containers/DeleteUserButton.js | 46 +++++++++ scm-ui/src/users/containers/UserRow.js | 8 ++ scm-ui/src/users/containers/Users.js | 10 +- scm-ui/src/users/modules/users.js | 95 ++++++++++++++++--- 6 files changed, 148 insertions(+), 21 deletions(-) create mode 100644 scm-ui/src/users/containers/DeleteUserButton.js diff --git a/scm-ui/src/apiclient.js b/scm-ui/src/apiclient.js index f6b04a59a1..02d3263fec 100644 --- a/scm-ui/src/apiclient.js +++ b/scm-ui/src/apiclient.js @@ -1,16 +1,14 @@ // @flow // get api base url from environment -const apiUrl = process.env.API_URL || process.env.PUBLIC_URL || ""; +const apiUrl = process.env.API_URL || process.env.PUBLIC_URL || "/scm"; export const PAGE_NOT_FOUND_ERROR = Error("page not found"); -// fetch does not send the X-Requested-With header (https://github.com/github/fetch/issues/17), -// but we need the header to detect ajax request (AjaxAwareAuthenticationRedirectStrategy). const fetchOptions: RequestOptions = { credentials: "same-origin", headers: { - "X-Requested-With": "XMLHttpRequest" + Cache: "no-cache" } }; @@ -40,7 +38,7 @@ class ApiClient { return this.httpRequestWithJSONBody(url, payload, "POST"); } - delete(url: string, payload: any) { + delete(url: string) { let options: RequestOptions = { method: "DELETE" }; diff --git a/scm-ui/src/repositories/containers/Repositories.js b/scm-ui/src/repositories/containers/Repositories.js index a7cd59e7de..b1f01c5b5b 100644 --- a/scm-ui/src/repositories/containers/Repositories.js +++ b/scm-ui/src/repositories/containers/Repositories.js @@ -3,7 +3,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { fetchRepositoriesIfNeeded } from '../modules/repositories'; -import { Link } from 'react-router-dom' +import { Link } from 'react-router-dom'; type Props = { diff --git a/scm-ui/src/users/containers/DeleteUserButton.js b/scm-ui/src/users/containers/DeleteUserButton.js new file mode 100644 index 0000000000..195e603ae8 --- /dev/null +++ b/scm-ui/src/users/containers/DeleteUserButton.js @@ -0,0 +1,46 @@ +// @flow +import React from "react"; +import { deleteUser } from '../modules/users'; +import {connect} from "react-redux"; + +type Props = { + user: any, + deleteUser: (username: string) => void +}; + +class DeleteUser extends React.Component { + + deleteUser = () => { + this.props.deleteUser(this.props.user.name); + }; + + render() { + if(this.props.user._links.delete) { + return ( + + + ); + } + } +} + +const mapStateToProps = state => { + return { + users: state.users.users + }; +}; + +const mapDispatchToProps = dispatch => { + return { + deleteUser: (username: string) => { + dispatch(deleteUser(username)); + } + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(DeleteUser); diff --git a/scm-ui/src/users/containers/UserRow.js b/scm-ui/src/users/containers/UserRow.js index d9184d3586..077ffa7fc3 100644 --- a/scm-ui/src/users/containers/UserRow.js +++ b/scm-ui/src/users/containers/UserRow.js @@ -1,11 +1,15 @@ // @flow import React from "react"; +import DeleteUserButton from "./DeleteUserButton"; type Props = { user: any }; + + export default class UserRow extends React.Component { + render() { return ( @@ -14,7 +18,11 @@ export default class UserRow extends React.Component { + + + + ); } } diff --git a/scm-ui/src/users/containers/Users.js b/scm-ui/src/users/containers/Users.js index 9a628c6fc2..0702a56245 100644 --- a/scm-ui/src/users/containers/Users.js +++ b/scm-ui/src/users/containers/Users.js @@ -11,12 +11,18 @@ type Props = { error: any, users: any, fetchUsersIfNeeded: () => void, - fetchUsers: () => void + fetchUsers: () => void, + fetchUsersIfNeeded: (url: string) => void, + }; class Users extends React.Component { componentWillMount() { - this.props.fetchUsers(); + this.props.fetchUsersIfNeeded(); + } + + componentDidUpdate() { + this.props.fetchUsersIfNeeded(); } render() { diff --git a/scm-ui/src/users/modules/users.js b/scm-ui/src/users/modules/users.js index 192e13e0eb..a2a157d1c9 100644 --- a/scm-ui/src/users/modules/users.js +++ b/scm-ui/src/users/modules/users.js @@ -1,10 +1,16 @@ // @flow +import {apiClient, PAGE_NOT_FOUND_ERROR} from '../../apiclient'; const FETCH_USERS = "scm/users/FETCH"; const FETCH_USERS_SUCCESS = "scm/users/FETCH_SUCCESS"; const FETCH_USERS_FAILURE = "scm/users/FETCH_FAILURE"; +const FETCH_USERS_NOTFOUND = 'scm/users/FETCH_NOTFOUND'; -const USERS_URL = "/scm/api/rest/v2/users"; +const DELETE_USER = "scm/users/DELETE"; +const DELETE_USER_SUCCESS = "scm/users/DELETE_SUCCESS"; +const DELETE_USER_FAILURE = "scm/users/DELETE_FAILURE"; + +const USERS_URL = "users"; function requestUsers() { return { @@ -12,15 +18,29 @@ function requestUsers() { }; } +function failedToFetchUsers(url: string, err: Error) { + return { + type: FETCH_USERS_FAILURE, + payload: err, + url + }; +} + +function usersNotFound(url: string) { + return { + type: FETCH_USERS_NOTFOUND, + url + }; +} + export function fetchUsers() { + return function(dispatch) { - // dispatch(requestUsers()); - return fetch(USERS_URL, { - credentials: "same-origin", - headers: { - Cache: "no-cache" - } - }) + dispatch(requestUsers()); + return apiClient.get(USERS_URL) + .then(response => { + return response; + }) .then(response => { if (response.ok) { return response.json(); @@ -28,8 +48,15 @@ export function fetchUsers() { }) .then(data => { dispatch(fetchUsersSuccess(data)); + }) + .catch((err) => { + if (err === PAGE_NOT_FOUND_ERROR) { + dispatch(usersNotFound(USERS_URL)); + } else { + dispatch(failedToFetchUsers(USERS_URL, err)); + } }); - }; + } } function fetchUsersSuccess(users: any) { @@ -40,8 +67,10 @@ function fetchUsersSuccess(users: any) { } export function shouldFetchUsers(state: any): boolean { - const users = state.users; - return null; + if(state.users.users == null){ + return true; + } + return false; } export function fetchUsersIfNeeded() { @@ -52,17 +81,52 @@ export function fetchUsersIfNeeded() { }; } + + +function requestDeleteUser(url: string) { + return { + type: DELETE_USER, + url + }; +} + +function deleteUserSuccess() { + return { + type: DELETE_USER_SUCCESS, + }; +} + +function deleteUserFailure(url: string, err: Error) { + return { + type: DELETE_USER_FAILURE, + payload: err, + url + }; +} + +export function deleteUser(username: string) { + return function(dispatch) { + dispatch(requestDeleteUser(username)); + return apiClient.delete(USERS_URL + '/' + username) + .then(() => { + dispatch(deleteUserSuccess()); + }) + .catch((err) => dispatch(deleteUserFailure(username, err))); + } +} + + + export default function reducer(state: any = {}, action: any = {}) { switch (action.type) { case FETCH_USERS: return { ...state, - users: [{ name: "" }] + users: null }; case FETCH_USERS_SUCCESS: return { ...state, - timestamp: action.timestamp, error: null, users: action.payload._embedded.users }; @@ -72,6 +136,11 @@ export default function reducer(state: any = {}, action: any = {}) { login: false, error: action.payload }; + case DELETE_USER_SUCCESS: + return { + ...state, + users: null + }; default: return state; From dc8ecd5689ded5278ae850217b3021bf2b140c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 10 Jul 2018 15:31:18 +0200 Subject: [PATCH 024/186] Adjust to repository storage with id --- .../scm/repository/AbstactImportHandler.java | 108 ++++---- .../scm/repository/NamespaceAndName.java | 50 ++++ .../java/sonia/scm/repository/Repository.java | 17 +- .../sonia/scm/repository/RepositoryDAO.java | 17 +- .../scm/repository/RepositoryManager.java | 27 +- .../RepositoryManagerDecorator.java | 34 +-- .../sonia/scm/repository/RepositoryUtil.java | 198 ++------------- .../api/RepositoryServiceFactory.java | 32 +-- .../scm/repository/spi/HookEventFacade.java | 45 +--- .../main/java/sonia/scm/util/HttpUtil.java | 20 +- .../scm/web/filter/RegexPermissionFilter.java | 133 ---------- .../sonia/scm/repository/RepositoryTest.java | 4 +- .../scm/repository/xml/XmlRepositoryDAO.java | 30 +-- .../repository/xml/XmlRepositoryDatabase.java | 129 ++-------- .../xml/XmlRepositoryMapAdapter.java | 7 +- .../java/sonia/scm/web/GitReceiveHook.java | 18 +- .../repository/GitRepositoryHandlerTest.java | 2 +- .../GitRepositoryPathMatcherTest.java | 22 +- .../spi/AbstractRemoteCommandTestBase.java | 19 +- .../scm/web/lfs/LfsBlobStoreFactoryTest.java | 9 +- .../lfs/servlet/LfsServletFactoryTest.java | 10 +- .../spi/HgHookChangesetProvider.java | 24 +- .../repository/spi/HgHookContextProvider.java | 15 +- .../sonia/scm/web/HgHookCallbackServlet.java | 85 ++----- .../repository/HgRepositoryHandlerTest.java | 2 +- .../spi/IncomingOutgoingTestBase.java | 21 +- .../scm/web/HgHookCallbackServletTest.java | 41 +++ .../scm/repository/SvnRepositoryHook.java | 17 +- .../repository/SvnRepositoryHandlerTest.java | 2 +- .../repository/DummyRepositoryHandler.java | 10 +- .../scm/repository/RepositoryBuilder.java | 49 ++++ .../scm/repository/RepositoryTestData.java | 82 ++---- .../SimpleRepositoryHandlerTestBase.java | 23 +- .../resources/RepositoryImportResource.java | 6 +- .../rest/resources/RepositoryResource.java | 36 --- .../repository/DefaultRepositoryManager.java | 86 ++++--- .../scm/repository/RepositoryMatcher.java | 36 ++- .../DefaultRepositoryManagerPerfTest.java | 17 +- .../DefaultRepositoryManagerTest.java | 237 +++++++----------- .../scm/repository/RepositoryMatcherTest.java | 19 +- 40 files changed, 619 insertions(+), 1120 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java delete mode 100644 scm-core/src/main/java/sonia/scm/web/filter/RegexPermissionFilter.java create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java create mode 100644 scm-test/src/main/java/sonia/scm/repository/RepositoryBuilder.java diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java index 2da506674d..6e0827af4a 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstactImportHandler.java @@ -36,19 +36,16 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Throwables; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.repository.ImportResult.Builder; -//~--- JDK imports ------------------------------------------------------------ - import java.io.File; import java.io.IOException; - import java.util.List; +//~--- JDK imports ------------------------------------------------------------ + /** * Abstract base class for directory based {@link ImportHandler} and * {@link AdvancedImportHandler}. @@ -164,23 +161,24 @@ public abstract class AbstactImportHandler implements AdvancedImportHandler logger.trace("search for repositories to import"); - try - { - - List repositoryNames = - RepositoryUtil.getRepositoryNames(getRepositoryHandler(), - getDirectoryNames()); - - for (String repositoryName : repositoryNames) - { - importRepository(manager, builder, throwExceptions, repositoryName); - } - - } - catch (IOException ex) - { - handleException(ex, throwExceptions); - } + // TODO #8783 +// try +// { +// +// List repositoryNames = +// RepositoryUtil.getRepositoryNames(getRepositoryHandler(), +// getDirectoryNames()); +// +// for (String repositoryName : repositoryNames) +// { +// importRepository(manager, builder, throwExceptions, repositoryName); +// } +// +// } +// catch (IOException ex) +// { +// handleException(ex, throwExceptions); +// } return builder.build(); } @@ -214,46 +212,48 @@ public abstract class AbstactImportHandler implements AdvancedImportHandler * @param manager * @param builder * @param throwExceptions - * @param repositoryName + * @param directoryName * * @throws IOException * @throws RepositoryException */ private void importRepository(RepositoryManager manager, Builder builder, - boolean throwExceptions, String repositoryName) + boolean throwExceptions, String directoryName) throws IOException, RepositoryException { - logger.trace("check repository {} for import", repositoryName); + logger.trace("check repository {} for import", directoryName); - Repository repository = manager.get(getTypeName(), repositoryName); - - if (repository == null) - { - try - { - importRepository(manager, repositoryName); - builder.addImportedDirectory(repositoryName); - } - catch (IOException ex) - { - builder.addFailedDirectory(repositoryName); - handleException(ex, throwExceptions); - } - catch (IllegalStateException ex) - { - builder.addFailedDirectory(repositoryName); - handleException(ex, throwExceptions); - } - catch (RepositoryException ex) - { - builder.addFailedDirectory(repositoryName); - handleException(ex, throwExceptions); - } - } - else if (logger.isDebugEnabled()) - { - logger.debug("repository {} is allready managed", repositoryName); - } + // TODO #8783 +// +// Repository repository = manager.get(namespaceAndName); +// +// if (repository == null) +// { +// try +// { +// importRepository(manager, repositoryName); +// builder.addImportedDirectory(repositoryName); +// } +// catch (IOException ex) +// { +// builder.addFailedDirectory(repositoryName); +// handleException(ex, throwExceptions); +// } +// catch (IllegalStateException ex) +// { +// builder.addFailedDirectory(repositoryName); +// handleException(ex, throwExceptions); +// } +// catch (RepositoryException ex) +// { +// builder.addFailedDirectory(repositoryName); +// handleException(ex, throwExceptions); +// } +// } +// else if (logger.isDebugEnabled()) +// { +// logger.debug("repository {} is already managed", repositoryName); +// } } /** diff --git a/scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java b/scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java new file mode 100644 index 0000000000..1aa6f474f0 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/NamespaceAndName.java @@ -0,0 +1,50 @@ +package sonia.scm.repository; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +import java.util.Objects; + +public class NamespaceAndName { + + private final String namespace; + private final String name; + + public NamespaceAndName(String namespace, String name) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(namespace), "a non empty namespace is required"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "a non empty name is required"); + this.namespace = namespace; + this.name = name; + } + + public String getNamespace() { + return namespace; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return getNamespace() + "/" + getName(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NamespaceAndName that = (NamespaceAndName) o; + return Objects.equals(namespace, that.namespace) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(namespace, name); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index df340564f6..b2d99c85e1 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -43,7 +43,12 @@ import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; import sonia.scm.util.ValidationUtil; -import javax.xml.bind.annotation.*; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -95,9 +100,10 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per * @param type type of the {@link Repository} * @param name name of the {@link Repository} */ - public Repository(String id, String type, String name) { + public Repository(String id, String type, String namespace, String name) { this.id = id; this.type = type; + this.namespace = namespace; this.name = name; } @@ -189,6 +195,11 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per public String getNamespace() { return namespace; } + @XmlTransient + public NamespaceAndName getNamespaceAndName() { + return new NamespaceAndName(getNamespace(), getName()); + } + public List getPermissions() { if (permissions == null) { permissions = Lists.newArrayList(); @@ -347,7 +358,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per public String createUrl(String baseUrl) { String url = HttpUtil.append(baseUrl, type); - return HttpUtil.append(url, name); + return HttpUtil.concatenate(url, namespace, name); } /** diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryDAO.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryDAO.java index 9a2d4b5662..ce309ecee6 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryDAO.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryDAO.java @@ -49,27 +49,22 @@ public interface RepositoryDAO extends GenericDAO /** * Returns true if a repository with specified - * type and name exists in the backend. + * namespace and name exists in the backend. * * - * @param type type of the repository - * @param name name of the repository + * @param namespaceAndName namespace and name of the repository * * @return true if the repository exists */ - public boolean contains(String type, String name); + boolean contains(NamespaceAndName namespaceAndName); //~--- get methods ---------------------------------------------------------- /** - * Returns the repository with the specified type and name or null + * Returns the repository with the specified namespace and name or null * if no such repository exists in the backend. * - * - * @param type - * @param name - * - * @return repository with the specified type and name or null + * @return repository with the specified namespace and name or null */ - public Repository get(String type, String name); + Repository get(NamespaceAndName namespaceAndName); } diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java index 7cbac2b52e..172108aa1b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryManager.java @@ -38,13 +38,11 @@ package sonia.scm.repository; import sonia.scm.Type; import sonia.scm.TypeManager; -//~--- JDK imports ------------------------------------------------------------ - +import javax.servlet.http.HttpServletRequest; import java.io.IOException; - import java.util.Collection; -import javax.servlet.http.HttpServletRequest; +//~--- JDK imports ------------------------------------------------------------ /** * The central class for managing {@link Repository} objects. @@ -83,18 +81,17 @@ public interface RepositoryManager //~--- get methods ---------------------------------------------------------- /** - * Returns a {@link Repository} by its type and name or + * Returns a {@link Repository} by its namespace and name or * null if the {@link Repository} could not be found. * * - * @param type type of the {@link Repository} - * @param name name of the {@link Repository} + * @param namespaceAndName namespace and name of the {@link Repository} * * - * @return {@link Repository} by its type and name or null + * @return {@link Repository} by its namespace and name or null * if the {@link Repository} could not be found */ - public Repository get(String type, String name); + public Repository get(NamespaceAndName namespaceAndName); /** * Returns all configured repository types. @@ -115,18 +112,6 @@ public interface RepositoryManager */ public Repository getFromRequest(HttpServletRequest request); - /** - * Returns the {@link Repository} associated to the given type and path. - * - * - * @param type type of the repository (hg, git ...) - * @param uri - * - * @return the {@link Repository} associated to the given type and path - * @since 1.9 - */ - public Repository getFromTypeAndUri(String type, String uri); - /** * Returns the {@link Repository} associated to the request uri. * diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java index 632b54b741..6990baf7c5 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryManagerDecorator.java @@ -38,13 +38,11 @@ package sonia.scm.repository; import sonia.scm.ManagerDecorator; import sonia.scm.Type; -//~--- JDK imports ------------------------------------------------------------ - +import javax.servlet.http.HttpServletRequest; import java.io.IOException; - import java.util.Collection; -import javax.servlet.http.HttpServletRequest; +//~--- JDK imports ------------------------------------------------------------ /** * Decorator for {@link RepositoryManager}. @@ -92,19 +90,10 @@ public class RepositoryManagerDecorator //~--- get methods ---------------------------------------------------------- - /** - * {@inheritDoc} - * - * - * @param type - * @param name - * - * @return - */ @Override - public Repository get(String type, String name) + public Repository get(NamespaceAndName namespaceAndName) { - return decorated.get(type, name); + return decorated.get(namespaceAndName); } /** @@ -146,21 +135,6 @@ public class RepositoryManagerDecorator return decorated.getFromRequest(request); } - /** - * {@inheritDoc} - * - * - * @param type - * @param uri - * - * @return - */ - @Override - public Repository getFromTypeAndUri(String type, String uri) - { - return decorated.getFromTypeAndUri(type, uri); - } - /** * {@inheritDoc} * diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java index e271992248..4bf10387d4 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java @@ -35,21 +35,20 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.io.DirectoryFileFilter; import sonia.scm.util.IOUtil; -//~--- JDK imports ------------------------------------------------------------ - import java.io.File; import java.io.IOException; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; +//~--- JDK imports ------------------------------------------------------------ + /** * @@ -82,204 +81,43 @@ public final class RepositoryUtil * * @return */ - public static List searchRepositoryDirectories(File directory, - String... names) - { - List repositories = new ArrayList(); + public static List searchRepositoryDirectories(File directory, String... names) { + List repositories = new ArrayList<>(); searchRepositoryDirectories(repositories, directory, Arrays.asList(names)); return repositories; } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * - * @param handler - * @param directoryPath - * @return - * - * @throws IOException - */ - public static String getRepositoryName(AbstractRepositoryHandler handler, - String directoryPath) - throws IOException - { - return getRepositoryName(handler.getConfig().getRepositoryDirectory(), - new File(directoryPath)); + public static String getRepositoryId(AbstractRepositoryHandler handler, String directoryPath) throws IOException { + return getRepositoryId(handler.getConfig().getRepositoryDirectory(), new File(directoryPath)); } - /** - * Method description - * - * - * - * @param config - * @param directoryPath - * @return - * - * @throws IOException - */ - public static String getRepositoryName(SimpleRepositoryConfig config, - String directoryPath) - throws IOException - { - return getRepositoryName(config.getRepositoryDirectory(), - new File(directoryPath)); + public static String getRepositoryId(AbstractRepositoryHandler handler, File directory) throws IOException { + return getRepositoryId(handler.getConfig(), directory); } - /** - * Method description - * - * - * - * @param handler - * @param directory - * @return - * - * @throws IOException - */ - public static String getRepositoryName(AbstractRepositoryHandler handler, - File directory) - throws IOException - { - return getRepositoryName(handler.getConfig().getRepositoryDirectory(), - directory); + public static String getRepositoryId(SimpleRepositoryConfig config, File directory) throws IOException { + return getRepositoryId(config.getRepositoryDirectory(), directory); } - /** - * Method description - * - * - * - * @param config - * @param directory - * @return - * - * @throws IOException - */ - public static String getRepositoryName(SimpleRepositoryConfig config, - File directory) - throws IOException - { - return getRepositoryName(config.getRepositoryDirectory(), directory); - } - - /** - * Method description - * - * - * - * @param baseDirectory - * @param directory - * @return - * - * @throws IOException - */ - public static String getRepositoryName(File baseDirectory, File directory) - throws IOException - { - String name = null; + public static String getRepositoryId(File baseDirectory, File directory) throws IOException { String path = directory.getCanonicalPath(); int directoryLength = baseDirectory.getCanonicalPath().length(); if (directoryLength < path.length()) { - name = IOUtil.trimSeperatorChars(path.substring(directoryLength)); - - // replace windows path seperator - name = name.replaceAll("\\\\", "/"); + String id = IOUtil.trimSeperatorChars(path.substring(directoryLength)); + Preconditions.checkState(!id.contains("\\") && !id.contains("/"), + "got illegal repository directory with separators in id: " + path); + return id; } - else if (logger.isWarnEnabled()) + else { - logger.warn("path is shorter as the main repository path"); + throw new IllegalStateException("path is shorter as the main repository path"); } - - return name; } - /** - * Method description - * - * - * @param handler - * @param directoryNames - * - * @return - * - * @throws IOException - */ - public static List getRepositoryNames( - AbstractRepositoryHandler handler, String... directoryNames) - throws IOException - { - return getRepositoryNames(handler.getConfig(), directoryNames); - } - - /** - * Method description - * - * - * @param config - * @param directoryNames - * - * @return - * - * @throws IOException - */ - public static List getRepositoryNames(SimpleRepositoryConfig config, - String... directoryNames) - throws IOException - { - return getRepositoryNames(config.getRepositoryDirectory(), directoryNames); - } - - /** - * Method description - * - * - * @param baseDirectory - * @param directoryNames - * - * @return - * - * @throws IOException - */ - public static List getRepositoryNames(File baseDirectory, - String... directoryNames) - throws IOException - { - List repositories = new ArrayList(); - List repositoryFiles = searchRepositoryDirectories(baseDirectory, - directoryNames); - - for (File file : repositoryFiles) - { - String name = getRepositoryName(baseDirectory, file); - - if (name != null) - { - repositories.add(name); - } - } - - return repositories; - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param repositories - * @param directory - * @param names - */ private static void searchRepositoryDirectories(List repositories, File directory, List names) { diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java index 5bd737a85a..90084b876e 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java @@ -37,20 +37,20 @@ package sonia.scm.repository.api; import com.github.legman.ReferenceType; import com.github.legman.Subscribe; - import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.google.inject.Singleton; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.HandlerEventType; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; import sonia.scm.config.ScmConfiguration; +import sonia.scm.event.ScmEventBus; +import sonia.scm.repository.ClearRepositoryCacheEvent; +import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.PostReceiveRepositoryHookEvent; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; @@ -63,11 +63,9 @@ import sonia.scm.repository.spi.RepositoryServiceProvider; import sonia.scm.repository.spi.RepositoryServiceResolver; import sonia.scm.security.ScmSecurityException; -//~--- JDK imports ------------------------------------------------------------ - import java.util.Set; -import sonia.scm.event.ScmEventBus; -import sonia.scm.repository.ClearRepositoryCacheEvent; + +//~--- JDK imports ------------------------------------------------------------ /** * The {@link RepositoryServiceFactory} is the entrypoint of the repository api. @@ -190,8 +188,7 @@ public final class RepositoryServiceFactory * Creates a new RepositoryService for the given repository. * * - * @param type type of the repository - * @param name name of the repository + * @param namespaceAndName namespace and name of the repository * * @return a implementation of RepositoryService * for the given type of repository @@ -204,24 +201,19 @@ public final class RepositoryServiceFactory * @throws ScmSecurityException if current user has not read permissions * for that repository */ - public RepositoryService create(String type, String name) + public RepositoryService create(NamespaceAndName namespaceAndName) throws RepositoryNotFoundException { - Preconditions.checkArgument(!Strings.isNullOrEmpty(type), - "a non empty type is required"); - Preconditions.checkArgument(!Strings.isNullOrEmpty(name), - "a non empty name is required"); + Preconditions.checkArgument(namespaceAndName != null, + "a non empty namespace and name is required"); - Repository repository = repositoryManager.get(type, name); + Repository repository = repositoryManager.get(namespaceAndName); if (repository == null) { - StringBuilder msg = - new StringBuilder("could not find a repository with type "); + String msg = "could not find a repository with namespace/name " + namespaceAndName; - msg.append(type).append(" and name ").append(name); - - throw new RepositoryNotFoundException(msg.toString()); + throw new RepositoryNotFoundException(msg); } return create(repository); diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java b/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java index 899892a5b7..93ed5f1111 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/HookEventFacade.java @@ -35,7 +35,7 @@ package sonia.scm.repository.spi; import com.google.inject.Inject; import com.google.inject.Provider; - +import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryException; import sonia.scm.repository.RepositoryHookEvent; @@ -72,50 +72,15 @@ public final class HookEventFacade //~--- methods -------------------------------------------------------------- - /** - * Method description - * - * - * @param id - * - * @return - * - * @throws RepositoryException - */ - public HookEventHandler handle(String id) throws RepositoryException - { + public HookEventHandler handle(String id) throws RepositoryException { return handle(repositoryManagerProvider.get().get(id)); } - /** - * Method description - * - * - * @param type - * @param repositoryName - * - * @return - * - * @throws RepositoryException - */ - public HookEventHandler handle(String type, String repositoryName) - throws RepositoryException - { - return handle(repositoryManagerProvider.get().get(type, repositoryName)); + public HookEventHandler handle(NamespaceAndName namespaceAndName) throws RepositoryException { + return handle(repositoryManagerProvider.get().get(namespaceAndName)); } - /** - * Method description - * - * - * @param repository - * - * @return - * - * @throws RepositoryException - */ - public HookEventHandler handle(Repository repository) - throws RepositoryException + public HookEventHandler handle(Repository repository) throws RepositoryException { if (repository == null) { diff --git a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java index 27abcaffbe..99de58023e 100644 --- a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java @@ -39,27 +39,23 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.CharMatcher; import com.google.common.base.Objects; import com.google.common.base.Strings; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.config.ScmConfiguration; -//~--- JDK imports ------------------------------------------------------------ - +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; - import java.net.URLDecoder; import java.net.URLEncoder; - +import java.util.Arrays; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +//~--- JDK imports ------------------------------------------------------------ /** * Util method for the http protocol. @@ -252,13 +248,15 @@ public final class HttpUtil //~--- methods -------------------------------------------------------------- + public static String concatenate(String... pathElements) { + return Arrays.stream(pathElements).reduce(HttpUtil::append).orElse(""); + } + /** * Appends the suffix to given uri. * - * - * @param uri uri + * @param uri uri * @param suffix suffix - * * @return * @since 1.9 */ diff --git a/scm-core/src/main/java/sonia/scm/web/filter/RegexPermissionFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/RegexPermissionFilter.java deleted file mode 100644 index 40c41a5954..0000000000 --- a/scm-core/src/main/java/sonia/scm/web/filter/RegexPermissionFilter.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm.web.filter; - -//~--- non-JDK imports -------------------------------------------------------- - - -import sonia.scm.config.ScmConfiguration; -import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryManager; - -//~--- JDK imports ------------------------------------------------------------ - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.servlet.http.HttpServletRequest; - -/** - * - * @author Sebastian Sdorra - */ -public abstract class RegexPermissionFilter extends PermissionFilter -{ - - /** Field description */ - public static final Pattern PATTERN_REPOSITORYNAME = - Pattern.compile("/[^/]+/([^/]+)(?:/.*)?"); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * - * @param configuration - * @param repositoryManager - */ - public RegexPermissionFilter(ScmConfiguration configuration, - RepositoryManager repositoryManager) - { - super(configuration); - this.repositoryManager = repositoryManager; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - protected abstract String getType(); - - /** - * Method description - * - * - * @param request - * - * @return - */ - @Override - protected Repository getRepository(HttpServletRequest request) - { - Repository repository = null; - String uri = request.getRequestURI(); - - uri = uri.substring(request.getContextPath().length()); - - Matcher m = PATTERN_REPOSITORYNAME.matcher(uri); - - if (m.matches()) - { - String repositoryname = m.group(1); - - repository = getRepository(repositoryname); - } - - return repository; - } - - /** - * Method description - * - * - * @param name - * - * @return - */ - protected Repository getRepository(String name) - { - return repositoryManager.get(getType(), name); - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private RepositoryManager repositoryManager; -} diff --git a/scm-core/src/test/java/sonia/scm/repository/RepositoryTest.java b/scm-core/src/test/java/sonia/scm/repository/RepositoryTest.java index 11bb602611..f13f4cbc67 100644 --- a/scm-core/src/test/java/sonia/scm/repository/RepositoryTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/RepositoryTest.java @@ -34,7 +34,7 @@ package sonia.scm.repository; import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * @@ -50,7 +50,7 @@ public class RepositoryTest @Test public void testCreateUrl() { - Repository repository = new Repository("123", "hg", "test/repo"); + Repository repository = new Repository("123", "hg", "test", "repo"); assertEquals("http://localhost:8080/scm/hg/test/repo", repository.createUrl("http://localhost:8080/scm")); diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java index 7c17c365aa..4510706721 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDAO.java @@ -36,11 +36,11 @@ package sonia.scm.repository.xml; import com.google.inject.Inject; import com.google.inject.Singleton; - +import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryDAO; -import sonia.scm.xml.AbstractXmlDAO; import sonia.scm.store.ConfigurationStoreFactory; +import sonia.scm.xml.AbstractXmlDAO; /** * @@ -71,36 +71,18 @@ public class XmlRepositoryDAO //~--- methods -------------------------------------------------------------- - /** - * Method description - * - * - * @param type - * @param name - * - * @return - */ @Override - public boolean contains(String type, String name) + public boolean contains(NamespaceAndName namespaceAndName) { - return db.contains(type, name); + return db.contains(namespaceAndName); } //~--- get methods ---------------------------------------------------------- - /** - * Method description - * - * - * @param type - * @param name - * - * @return - */ @Override - public Repository get(String type, String name) + public Repository get(NamespaceAndName namespaceAndName) { - return db.get(type, name); + return db.get(namespaceAndName); } //~--- methods -------------------------------------------------------------- diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDatabase.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDatabase.java index a5c599c291..1c82b07f50 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDatabase.java +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryDatabase.java @@ -35,34 +35,26 @@ package sonia.scm.repository.xml; //~--- non-JDK imports -------------------------------------------------------- +import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Repository; import sonia.scm.xml.XmlDatabase; -//~--- JDK imports ------------------------------------------------------------ - -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +//~--- JDK imports ------------------------------------------------------------ -/** - * - * @author Sebastian Sdorra - */ @XmlRootElement(name = "repository-db") @XmlAccessorType(XmlAccessType.FIELD) public class XmlRepositoryDatabase implements XmlDatabase { - /** - * Constructs ... - * - */ public XmlRepositoryDatabase() { long c = System.currentTimeMillis(); @@ -71,124 +63,53 @@ public class XmlRepositoryDatabase implements XmlDatabase lastModified = c; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param type - * @param name - * - * @return - */ - static String createKey(String type, String name) + static String createKeyX(NamespaceAndName namespaceAndName) { - return type.concat(":").concat(name); + return namespaceAndName.getNamespace() + ":" + namespaceAndName.getName(); } - /** - * Method description - * - * - * @param repository - * - * @return - */ - static String createKey(Repository repository) + static String createKeyX(Repository repository) { - return createKey(repository.getType(), repository.getName()); + return createKeyX(repository.getNamespaceAndName()); } - /** - * Method description - * - * - * @param repository - */ @Override public void add(Repository repository) { - repositoryMap.put(createKey(repository), repository); + repositoryMap.put(createKeyX(repository), repository); } - /** - * Method description - * - * - * - * @param type - * @param name - * - * @return - */ - public boolean contains(String type, String name) + public boolean contains(NamespaceAndName namespaceAndName) { - return repositoryMap.containsKey(createKey(type, name)); + return repositoryMap.containsKey(createKeyX(namespaceAndName)); } - /** - * Method description - * - * - * @param id - * - * @return - */ @Override public boolean contains(String id) { return get(id) != null; } - /** - * Method description - * - * - * @param repository - * - * @return - */ public boolean contains(Repository repository) { - return repositoryMap.containsKey(createKey(repository)); + return repositoryMap.containsKey(createKeyX(repository)); } - /** - * Method description - * - * - * @param repository - */ - public void remove(Repository repository) + public void removeX(Repository repository) { - repositoryMap.remove(createKey(repository)); + repositoryMap.remove(createKeyX(repository)); } - /** - * Method description - * - * - * @param id - * - * @return - */ @Override public Repository remove(String id) { Repository r = get(id); - remove(r); + removeX(r); return r; } - /** - * Method description - * - * - * @return - */ @Override public Collection values() { @@ -197,18 +118,9 @@ public class XmlRepositoryDatabase implements XmlDatabase //~--- get methods ---------------------------------------------------------- - /** - * Method description - * - * - * @param type - * @param name - * - * @return - */ - public Repository get(String type, String name) + public Repository get(NamespaceAndName namespaceAndName) { - return repositoryMap.get(createKey(type, name)); + return repositoryMap.get(createKeyX(namespaceAndName)); } /** @@ -298,6 +210,5 @@ public class XmlRepositoryDatabase implements XmlDatabase /** Field description */ @XmlJavaTypeAdapter(XmlRepositoryMapAdapter.class) @XmlElement(name = "repositories") - private Map repositoryMap = new LinkedHashMap(); + private Map repositoryMap = new LinkedHashMap<>(); } diff --git a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryMapAdapter.java b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryMapAdapter.java index a90a2d4fa9..4706c64f62 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryMapAdapter.java +++ b/scm-dao-xml/src/main/java/sonia/scm/repository/xml/XmlRepositoryMapAdapter.java @@ -37,12 +37,11 @@ package sonia.scm.repository.xml; import sonia.scm.repository.Repository; -//~--- JDK imports ------------------------------------------------------------ - +import javax.xml.bind.annotation.adapters.XmlAdapter; import java.util.LinkedHashMap; import java.util.Map; -import javax.xml.bind.annotation.adapters.XmlAdapter; +//~--- JDK imports ------------------------------------------------------------ /** * @@ -88,7 +87,7 @@ public class XmlRepositoryMapAdapter for (Repository repository : repositories) { - repositoryMap.put(XmlRepositoryDatabase.createKey(repository), + repositoryMap.put(XmlRepositoryDatabase.createKeyX(repository), repository); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java index 3ecd6047e9..eeda60ed02 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java @@ -40,24 +40,21 @@ import org.eclipse.jgit.transport.PostReceiveHook; import org.eclipse.jgit.transport.PreReceiveHook; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceivePack; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.RepositoryUtil; import sonia.scm.repository.spi.GitHookContextProvider; import sonia.scm.repository.spi.HookEventFacade; -//~--- JDK imports ------------------------------------------------------------ - import java.io.File; import java.io.IOException; - import java.util.Collection; import java.util.List; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -131,15 +128,14 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook try { Repository repository = rpack.getRepository(); - String repositoryName = resolveRepositoryName(repository); + String id = resolveRepositoryId(repository); - logger.trace("resolved repository name to {}", repositoryName); + logger.trace("resolved repository to id {}", id); GitHookContextProvider context = new GitHookContextProvider(rpack, receiveCommands); - hookEventFacade.handle(GitRepositoryHandler.TYPE_NAME, - repositoryName).fireHookEvent(type, context); + hookEventFacade.handle(id).fireHookEvent(type, context); } catch (Exception ex) @@ -191,7 +187,7 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook * * @throws IOException */ - private String resolveRepositoryName(Repository repository) throws IOException + private String resolveRepositoryId(Repository repository) throws IOException { File directory; @@ -204,7 +200,7 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook directory = repository.getWorkTree(); } - return RepositoryUtil.getRepositoryName(handler, directory); + return RepositoryUtil.getRepositoryId(handler, directory); } //~--- fields --------------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java index d5201dca83..47ca08b597 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryHandlerTest.java @@ -106,7 +106,7 @@ public class GitRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { gitConfig.setRepositoryDirectory(new File("/path")); repositoryHandler.setConfig(gitConfig); - Repository repository = new Repository("id", "git", "Name"); + Repository repository = new Repository("id", "git", "Space", "Name"); File path = repositoryHandler.getDirectory(repository); assertEquals("/path/id", path.getAbsolutePath()); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryPathMatcherTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryPathMatcherTest.java index 7adc4a6913..598d3b6400 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryPathMatcherTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryPathMatcherTest.java @@ -31,7 +31,9 @@ package sonia.scm.repository; import org.junit.Test; -import static org.junit.Assert.*; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * Unit tests for {@link GitRepositoryPathMatcher}. @@ -45,18 +47,18 @@ public class GitRepositoryPathMatcherTest { @Test public void testIsPathMatching() { - assertFalse(pathMatcher.isPathMatching(repository("my-repo"), "my-repoo")); - assertFalse(pathMatcher.isPathMatching(repository("my"), "my-repo")); - assertFalse(pathMatcher.isPathMatching(repository("my"), "my-repo/with/path")); + assertFalse(pathMatcher.isPathMatching(repository("space", "my-repo"), "my-repoo")); + assertFalse(pathMatcher.isPathMatching(repository("space", "my"), "my-repo")); + assertFalse(pathMatcher.isPathMatching(repository("space", "my"), "my-repo/with/path")); - assertTrue(pathMatcher.isPathMatching(repository("my-repo"), "my-repo")); - assertTrue(pathMatcher.isPathMatching(repository("my-repo"), "my-repo.git")); - assertTrue(pathMatcher.isPathMatching(repository("my-repo"), "my-repo/with/path")); - assertTrue(pathMatcher.isPathMatching(repository("my-repo"), "my-repo.git/with/path")); + assertTrue(pathMatcher.isPathMatching(repository("space", "my-repo"), "my-repo")); + assertTrue(pathMatcher.isPathMatching(repository("space", "my-repo"), "my-repo.git")); + assertTrue(pathMatcher.isPathMatching(repository("space", "my-repo"), "my-repo/with/path")); + assertTrue(pathMatcher.isPathMatching(repository("space", "my-repo"), "my-repo.git/with/path")); } - private Repository repository(String name) { - return new Repository(name, GitRepositoryHandler.TYPE_NAME, name); + private Repository repository(String namespace, String name) { + return new Repository(name, GitRepositoryHandler.TYPE_NAME, namespace, name); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java index a76d110561..e2a401bf7d 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java @@ -38,34 +38,31 @@ package sonia.scm.repository.spi; import com.google.common.base.Charsets; import com.google.common.io.Files; import com.google.inject.Provider; - import org.eclipse.jgit.api.CommitCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.ScmTransportProtocol; import org.eclipse.jgit.transport.Transport; - import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.rules.TemporaryFolder; - import sonia.scm.repository.Changeset; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.Repository; import sonia.scm.user.User; import sonia.scm.user.UserTestData; -import static org.junit.Assert.*; - -import static org.mockito.Mockito.*; - -//~--- JDK imports ------------------------------------------------------------ - import java.io.File; import java.io.IOException; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -88,8 +85,8 @@ public class AbstractRemoteCommandTestBase outgoingDirectory = tempFolder.newFile("outgoing"); outgoingDirectory.delete(); - incomgingRepository = new Repository("1", "git", "incoming"); - outgoingRepository = new Repository("2", "git", "outgoing"); + incomgingRepository = new Repository("1", "git", "space", "incoming"); + outgoingRepository = new Repository("2", "git", "space", "outgoing"); incoming = Git.init().setDirectory(incomingDirectory).setBare(false).call(); outgoing = Git.init().setDirectory(outgoingDirectory).setBare(false).call(); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java index 2eb8968405..fe48e403c5 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java @@ -35,14 +35,15 @@ package sonia.scm.web.lfs; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; -import static org.mockito.Matchers.matches; import org.mockito.Mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.repository.Repository; import sonia.scm.store.BlobStoreFactory; +import static org.mockito.Matchers.matches; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + /** * Unit tests for {@link LfsBlobStoreFactory}. * @@ -59,7 +60,7 @@ public class LfsBlobStoreFactoryTest { @Test public void getBlobStore() throws Exception { - lfsBlobStoreFactory.getLfsBlobStore(new Repository("the-id", "GIT", "the-name")); + lfsBlobStoreFactory.getLfsBlobStore(new Repository("the-id", "GIT", "space", "the-name")); // just make sure the right parameter is passed, as properly validating the return value is nearly impossible with // the return value (and should not be part of this test) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java index c670032089..74674cc9db 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java @@ -7,8 +7,9 @@ import javax.servlet.http.HttpServletRequest; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; /** * Created by omilke on 18.05.2017. @@ -18,14 +19,15 @@ public class LfsServletFactoryTest { @Test public void buildBaseUri() throws Exception { + String repositoryNamespace = "space"; String repositoryName = "git-lfs-demo"; - String result = LfsServletFactory.buildBaseUri(new Repository("", "GIT", repositoryName), RequestWithUri(repositoryName, true)); + String result = LfsServletFactory.buildBaseUri(new Repository("", "GIT", repositoryNamespace, repositoryName), RequestWithUri(repositoryName, true)); assertThat(result, is(equalTo("http://localhost:8081/scm/git/git-lfs-demo.git/info/lfs/objects/"))); //result will be with dot-gix suffix, ide - result = LfsServletFactory.buildBaseUri(new Repository("", "GIT", repositoryName), RequestWithUri(repositoryName, false)); + result = LfsServletFactory.buildBaseUri(new Repository("", "GIT", repositoryNamespace, repositoryName), RequestWithUri(repositoryName, false)); assertThat(result, is(equalTo("http://localhost:8081/scm/git/git-lfs-demo.git/info/lfs/objects/"))); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java index 17c0816686..59ad0c3345 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookChangesetProvider.java @@ -34,20 +34,18 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import com.aragost.javahg.Repository; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.spi.javahg.HgLogChangesetCommand; import sonia.scm.web.HgUtil; -//~--- JDK imports ------------------------------------------------------------ - import java.io.File; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -63,22 +61,12 @@ public class HgHookChangesetProvider implements HookChangesetProvider //~--- constructors --------------------------------------------------------- - /** - * Constructs ... - * - * - * @param handler - * @param repositoryName - * @param hookManager - * @param startRev - * @param type - */ public HgHookChangesetProvider(HgRepositoryHandler handler, - String repositoryName, HgHookManager hookManager, String startRev, + String id, HgHookManager hookManager, String startRev, RepositoryHookType type) { this.handler = handler; - this.repositoryName = repositoryName; + this.id = id; this.hookManager = hookManager; this.startRev = startRev; this.type = type; @@ -136,7 +124,7 @@ public class HgHookChangesetProvider implements HookChangesetProvider private Repository open() { File directory = handler.getConfig().getRepositoryDirectory(); - File repositoryDirectory = new File(directory, repositoryName); + File repositoryDirectory = new File(directory, id); // use HG_PENDING only for pre receive hooks boolean pending = type == RepositoryHookType.PRE_RECEIVE; @@ -155,7 +143,7 @@ public class HgHookChangesetProvider implements HookChangesetProvider private HgHookManager hookManager; /** Field description */ - private String repositoryName; + private String id; /** Field description */ private HookChangesetResponse response; diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java index dede59796c..5b354ecec4 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgHookContextProvider.java @@ -38,16 +38,16 @@ import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.api.HgHookBranchProvider; import sonia.scm.repository.api.HgHookMessageProvider; +import sonia.scm.repository.api.HgHookTagProvider; import sonia.scm.repository.api.HookBranchProvider; import sonia.scm.repository.api.HookFeature; import sonia.scm.repository.api.HookMessageProvider; - -//~--- JDK imports ------------------------------------------------------------ +import sonia.scm.repository.api.HookTagProvider; import java.util.EnumSet; import java.util.Set; -import sonia.scm.repository.api.HgHookTagProvider; -import sonia.scm.repository.api.HookTagProvider; + +//~--- JDK imports ------------------------------------------------------------ /** * Mercurial implementation of {@link HookContextProvider}. @@ -67,17 +67,16 @@ public class HgHookContextProvider extends HookContextProvider * Constructs a new instance. * * @param handler mercurial repository handler - * @param repositoryName name of changed repository + * @param namespaceAndName namespace and name of changed repository * @param hookManager mercurial hook manager * @param startRev start revision * @param type type of hook */ public HgHookContextProvider(HgRepositoryHandler handler, - String repositoryName, HgHookManager hookManager, String startRev, + String id, HgHookManager hookManager, String startRev, RepositoryHookType type) { - this.hookChangesetProvider = new HgHookChangesetProvider(handler, - repositoryName, hookManager, startRev, type); + this.hookChangesetProvider = new HgHookChangesetProvider(handler, id, hookManager, startRev, type); } //~--- get methods ---------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java index abbf09380a..4283519011 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java @@ -40,13 +40,10 @@ import com.google.common.io.Closeables; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; - import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.repository.HgContext; import sonia.scm.repository.HgHookManager; import sonia.scm.repository.HgRepositoryHandler; @@ -62,19 +59,17 @@ import sonia.scm.security.Tokens; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; -//~--- JDK imports ------------------------------------------------------------ - -import java.io.IOException; -import java.io.PrintWriter; - -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +//~--- JDK imports ------------------------------------------------------------ /** * @@ -187,7 +182,7 @@ public class HgHookCallbackServlet extends HttpServlet if (m.matches()) { - String repositoryId = getRepositoryName(request); + String id = getRepositoryId(request); String type = m.group(1); String challenge = request.getParameter(PARAM_CHALLENGE); @@ -204,7 +199,7 @@ public class HgHookCallbackServlet extends HttpServlet authenticate(request, credentials); } - hookCallback(response, repositoryId, type, challenge, node); + hookCallback(response, id, type, challenge, node); } else if (logger.isDebugEnabled()) { @@ -227,13 +222,6 @@ public class HgHookCallbackServlet extends HttpServlet } } - /** - * Method description - * - * - * @param request - * @param credentials - */ private void authenticate(HttpServletRequest request, String credentials) { try @@ -270,18 +258,7 @@ public class HgHookCallbackServlet extends HttpServlet } } - /** - * Method description - * - * - * @param response - * @param repositoryName - * @param node - * @param type - * - * @throws IOException - */ - private void fireHook(HttpServletResponse response, String repositoryName, + private void fireHook(HttpServletResponse response, String id, String node, RepositoryHookType type) throws IOException { @@ -294,11 +271,10 @@ public class HgHookCallbackServlet extends HttpServlet contextProvider.get().setPending(true); } - context = new HgHookContextProvider(handler, repositoryName, hookManager, + context = new HgHookContextProvider(handler, id, hookManager, node, type); - hookEventFacade.handle(HgRepositoryHandler.TYPE_NAME, - repositoryName).fireHookEvent(type, context); + hookEventFacade.handle(id).fireHookEvent(type, context); printMessages(response, context); } @@ -306,7 +282,7 @@ public class HgHookCallbackServlet extends HttpServlet { if (logger.isErrorEnabled()) { - logger.error("could not find repository {}", repositoryName); + logger.error("could not find repository with id {}", id); if (logger.isTraceEnabled()) { @@ -322,22 +298,7 @@ public class HgHookCallbackServlet extends HttpServlet } } - /** - * Method description - * - * - * @param response - * @param repositoryName - * @param typeName - * @param challenge - * @param node - * - * @throws IOException - */ - private void hookCallback(HttpServletResponse response, - String repositoryName, String typeName, String challenge, String node) - throws IOException - { + private void hookCallback(HttpServletResponse response, String id, String typeName, String challenge, String node) throws IOException { if (hookManager.isAcceptAble(challenge)) { RepositoryHookType type = null; @@ -353,7 +314,7 @@ public class HgHookCallbackServlet extends HttpServlet if (type != null) { - fireHook(response, repositoryName, node, type); + fireHook(response, id, node, type); } else { @@ -403,12 +364,12 @@ public class HgHookCallbackServlet extends HttpServlet * Method description * * - * @param resonse + * @param response * @param context * * @throws IOException */ - private void printMessages(HttpServletResponse resonse, + private void printMessages(HttpServletResponse response, HgHookContextProvider context) throws IOException { @@ -420,7 +381,7 @@ public class HgHookCallbackServlet extends HttpServlet try { - writer = resonse.getWriter(); + writer = response.getWriter(); printMessages(writer, msgs); } @@ -506,9 +467,9 @@ public class HgHookCallbackServlet extends HttpServlet * * @return */ - private String getRepositoryName(HttpServletRequest request) + private String getRepositoryId(HttpServletRequest request) { - String name = null; + String id = null; String path = request.getParameter(PARAM_REPOSITORYPATH); if (Util.isNotEmpty(path)) @@ -520,11 +481,11 @@ public class HgHookCallbackServlet extends HttpServlet */ try { - name = RepositoryUtil.getRepositoryName(handler, path); + id = RepositoryUtil.getRepositoryId(handler, path); } catch (IOException ex) { - logger.error("could not find name of repository", ex); + logger.error("could not find namespace and name of repository", ex); } } else if (logger.isWarnEnabled()) @@ -532,7 +493,7 @@ public class HgHookCallbackServlet extends HttpServlet logger.warn("no repository path parameter found"); } - return name; + return id; } //~--- fields --------------------------------------------------------------- diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java index c5d62dc054..bd14a63b56 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/HgRepositoryHandlerTest.java @@ -101,7 +101,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { hgConfig.setPythonBinary("python"); repositoryHandler.setConfig(hgConfig); - Repository repository = new Repository("id", "git", "Name"); + Repository repository = new Repository("id", "git", "Space", "Name"); File path = repositoryHandler.getDirectory(repository); assertEquals("/path/id", path.getAbsolutePath()); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/IncomingOutgoingTestBase.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/IncomingOutgoingTestBase.java index c45d0af2da..8dca8fdfe6 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/IncomingOutgoingTestBase.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/IncomingOutgoingTestBase.java @@ -41,14 +41,11 @@ import com.aragost.javahg.Repository; import com.aragost.javahg.RepositoryConfiguration; import com.aragost.javahg.commands.AddCommand; import com.aragost.javahg.commands.CommitCommand; - import com.google.common.base.Charsets; import com.google.common.io.Files; - import org.junit.Before; import org.junit.Rule; import org.junit.rules.TemporaryFolder; - import sonia.scm.AbstractTestBase; import sonia.scm.repository.HgConfig; import sonia.scm.repository.HgContext; @@ -58,15 +55,15 @@ import sonia.scm.user.User; import sonia.scm.user.UserTestData; import sonia.scm.util.MockUtil; -import static org.junit.Assert.*; - -import static org.mockito.Mockito.*; - -//~--- JDK imports ------------------------------------------------------------ - import java.io.File; import java.io.IOException; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -90,10 +87,8 @@ public abstract class IncomingOutgoingTestBase extends AbstractTestBase incomingDirectory = tempFolder.newFolder("incoming"); outgoingDirectory = tempFolder.newFolder("outgoing"); - incomingRepository = new sonia.scm.repository.Repository("1", "hg", - "incoming"); - outgoingRepository = new sonia.scm.repository.Repository("2", "hg", - "outgoing"); + incomingRepository = new sonia.scm.repository.Repository("1", "hg", "space", "incoming"); + outgoingRepository = new sonia.scm.repository.Repository("2", "hg", "space", "outgoing"); incoming = Repository.create(createConfig(temp), incomingDirectory); outgoing = Repository.create(createConfig(temp), outgoingDirectory); diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java new file mode 100644 index 0000000000..a586962bb8 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/web/HgHookCallbackServletTest.java @@ -0,0 +1,41 @@ +package sonia.scm.web; + +import org.junit.Test; +import sonia.scm.repository.HgConfig; +import sonia.scm.repository.HgRepositoryHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static sonia.scm.web.HgHookCallbackServlet.PARAM_REPOSITORYPATH; + +public class HgHookCallbackServletTest { + + @Test + public void shouldExtractCorrectRepositoryId() throws ServletException, IOException { + HgRepositoryHandler handler = mock(HgRepositoryHandler.class); + HgHookCallbackServlet servlet = new HgHookCallbackServlet(null, handler, null, null); + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + HgConfig config = mock(HgConfig.class); + + when(request.getContextPath()).thenReturn("http://example.com/scm"); + when(request.getRequestURI()).thenReturn("http://example.com/scm/hook/hg/pretxnchangegroup"); + when(request.getParameter(PARAM_REPOSITORYPATH)).thenReturn("/tmp/hg/12345"); + + when(handler.getConfig()).thenReturn(config); + when(config.getRepositoryDirectory()).thenReturn(new File("/tmp/hg")); + + servlet.doPost(request, response); + + verify(response, never()).sendError(anyInt()); + } +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHook.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHook.java index 1de3b2aecb..00958174a4 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHook.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnRepositoryHook.java @@ -37,7 +37,6 @@ package sonia.scm.repository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.tmatesoft.svn.core.SVNCancelException; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; @@ -45,21 +44,19 @@ import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.internal.io.fs.FSHook; import org.tmatesoft.svn.core.internal.io.fs.FSHookEvent; import org.tmatesoft.svn.core.internal.io.fs.FSHooks; - import sonia.scm.repository.spi.AbstractSvnHookChangesetProvider; import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.repository.spi.SvnHookContextProvider; import sonia.scm.repository.spi.SvnPostReceiveHookChangesetProvier; import sonia.scm.repository.spi.SvnPreReceiveHookChangesetProvier; import sonia.scm.util.AssertUtil; -import sonia.scm.util.IOUtil; import sonia.scm.util.Util; -//~--- JDK imports ------------------------------------------------------------ - import java.io.File; import java.io.IOException; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -166,12 +163,10 @@ public class SvnRepositoryHook implements FSHook { try { - String name = getRepositoryName(directory); - - name = IOUtil.trimSeperatorChars(name); + String id = getRepositoryId(directory); //J- - hookEventFacade.handle(SvnRepositoryHandler.TYPE_NAME, name) + hookEventFacade.handle(id) .fireHookEvent( changesetProvider.getType(), new SvnHookContextProvider(changesetProvider) @@ -202,11 +197,11 @@ public class SvnRepositoryHook implements FSHook * * @throws IOException */ - private String getRepositoryName(File directory) throws IOException + private String getRepositoryId(File directory) throws IOException { AssertUtil.assertIsNotNull(directory); - return RepositoryUtil.getRepositoryName(handler, directory); + return RepositoryUtil.getRepositoryId(handler, directory); } //~--- fields --------------------------------------------------------------- diff --git a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java index c7cd78dc75..f2c53e413c 100644 --- a/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java +++ b/scm-plugins/scm-svn-plugin/src/test/java/sonia/scm/repository/SvnRepositoryHandlerTest.java @@ -113,7 +113,7 @@ public class SvnRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase { svnConfig.setRepositoryDirectory(new File("/path")); repositoryHandler.setConfig(svnConfig); - Repository repository = new Repository("id", "svn", "Name"); + Repository repository = new Repository("id", "svn", "Space", "Name"); File path = repositoryHandler.getDirectory(repository); assertEquals("/path/id", path.getAbsolutePath()); diff --git a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java index fa2cc1b0fd..870127b14e 100644 --- a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java +++ b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java @@ -56,7 +56,7 @@ public class DummyRepositoryHandler public static final Type TYPE = new Type(TYPE_NAME, TYPE_DISPLAYNAME); - private Set existingRepoNames = new HashSet<>(); + private final Set existingRepoNames = new HashSet<>(); public DummyRepositoryHandler(ConfigurationStoreFactory storeFactory) { super(storeFactory, new DefaultFileSystem()); @@ -69,12 +69,12 @@ public class DummyRepositoryHandler @Override - protected void create(Repository repository, File directory) - throws RepositoryException { - if (existingRepoNames.contains(repository.getNamespace() + repository.getName())) { + protected void create(Repository repository, File directory) throws RepositoryException { + String key = repository.getNamespace() + "/" + repository.getName(); + if (existingRepoNames.contains(key)) { throw new RepositoryAlreadyExistsException("Repo exists"); } else { - existingRepoNames.add(repository.getNamespace() + repository.getName()); + existingRepoNames.add(key); } } diff --git a/scm-test/src/main/java/sonia/scm/repository/RepositoryBuilder.java b/scm-test/src/main/java/sonia/scm/repository/RepositoryBuilder.java new file mode 100644 index 0000000000..9c8ceb4762 --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/repository/RepositoryBuilder.java @@ -0,0 +1,49 @@ +package sonia.scm.repository; + +public class RepositoryBuilder { + + private String id = "id-" + ++nextID; + private String contact = "test@example.com"; + private String description = ""; + private String namespace = "test"; + private String name = "name"; + private String type = "git"; + + private static int nextID = 0; + + public RepositoryBuilder type(String type) { + this.type = type; + return this; + } + + public RepositoryBuilder contact(String contact) { + this.contact = contact; + return this; + } + + public RepositoryBuilder namespace(String namespace) { + this.namespace = namespace; + return this; + } + + public RepositoryBuilder name(String name) { + this.name = name; + return this; + } + + public RepositoryBuilder description(String description) { + this.description = description; + return this; + } + + public Repository build() { + Repository repository = new Repository(); + repository.setId(id); + repository.setType(type); + repository.setContact(contact); + repository.setNamespace(namespace); + repository.setName(name); + repository.setDescription(description); + return repository; + } +} diff --git a/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java b/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java index 65fad6f80d..b81c39ca00 100644 --- a/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java +++ b/scm-test/src/main/java/sonia/scm/repository/RepositoryTestData.java @@ -6,13 +6,13 @@ * 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. + * 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. + * 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. + * 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 @@ -26,26 +26,22 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * http://bitbucket.org/sdorra/scm-manager - * */ - package sonia.scm.repository; -public final class RepositoryTestData -{ +public final class RepositoryTestData { - private RepositoryTestData() {} + private RepositoryTestData() { + } - public static Repository create42Puzzle() - { + public static Repository create42Puzzle() { return create42Puzzle(DummyRepositoryHandler.TYPE_NAME); } - public static Repository create42Puzzle(String type) - { - return new Builder() + public static Repository create42Puzzle(String type) { + return new RepositoryBuilder() .type(type) .contact("douglas.adams@hitchhiker.com") .name("42Puzzle") @@ -53,15 +49,13 @@ public final class RepositoryTestData .build(); } - public static Repository createHappyVerticalPeopleTransporter() - { + public static Repository createHappyVerticalPeopleTransporter() { return createHappyVerticalPeopleTransporter( DummyRepositoryHandler.TYPE_NAME); } - public static Repository createHappyVerticalPeopleTransporter(String type) - { - return new Builder() + public static Repository createHappyVerticalPeopleTransporter(String type) { + return new RepositoryBuilder() .type(type) .contact("zaphod.beeblebrox@hitchhiker.com") .name("happyVerticalPeopleTransporter") @@ -69,14 +63,12 @@ public final class RepositoryTestData .build(); } - public static Repository createHeartOfGold() - { + public static Repository createHeartOfGold() { return createHeartOfGold(DummyRepositoryHandler.TYPE_NAME); } - public static Repository createHeartOfGold(String type) - { - return new Builder() + public static Repository createHeartOfGold(String type) { + return new RepositoryBuilder() .type(type) .contact("zaphod.beeblebrox@hitchhiker.com") .name("HeartOfGold") @@ -85,51 +77,17 @@ public final class RepositoryTestData .build(); } - public static Repository createRestaurantAtTheEndOfTheUniverse() - { + public static Repository createRestaurantAtTheEndOfTheUniverse() { return createRestaurantAtTheEndOfTheUniverse( DummyRepositoryHandler.TYPE_NAME); } - public static Repository createRestaurantAtTheEndOfTheUniverse(String type) - { - return new Builder() + public static Repository createRestaurantAtTheEndOfTheUniverse(String type) { + return new RepositoryBuilder() .type(type) .contact("douglas.adams@hitchhiker.com") .name("RestaurantAtTheEndOfTheUniverse") .description("The Restaurant at the End of the Universe") .build(); } - - private static class Builder { - private static int nextID = 0; - Repository repository = new Repository(); - { - repository.setId("ID-" + ++nextID); - } - - Builder type(String type) { - repository.setType(type); - return this; - } - - Builder contact(String contact) { - repository.setContact(contact); - return this; - } - - Builder name(String name) { - repository.setName(name); - return this; - } - - Builder description(String description) { - repository.setDescription(description); - return this; - } - - public Repository build() { - return repository; - } - } } diff --git a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java index 07a9495588..680ac83dd9 100644 --- a/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java +++ b/scm-test/src/main/java/sonia/scm/repository/SimpleRepositoryHandlerTestBase.java @@ -40,9 +40,10 @@ import sonia.scm.store.InMemoryConfigurationStoreFactory; import sonia.scm.util.IOUtil; import java.io.File; -import java.io.IOException; -import static org.junit.Assert.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; //~--- JDK imports ------------------------------------------------------------ @@ -59,19 +60,12 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { ConfigurationStoreFactory factory, File directory); @Test - public void testCreate() throws RepositoryException, IOException { - createRepository(); - } - - @Test(expected = RepositoryAlreadyExistsException.class) - public void testCreateExisitingRepository() - throws RepositoryException, IOException { - createRepository(); + public void testCreate() throws RepositoryException { createRepository(); } @Test - public void testCreateResourcePath() throws RepositoryException, IOException { + public void testCreateResourcePath() throws RepositoryException { Repository repository = createRepository(); String path = handler.createResourcePath(repository); @@ -81,7 +75,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { } @Test - public void testDelete() throws RepositoryException, IOException { + public void testDelete() throws RepositoryException { Repository repository = createRepository(); handler.delete(repository); @@ -92,7 +86,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { } @Override - protected void postSetUp() throws Exception { + protected void postSetUp() { InMemoryConfigurationStoreFactory storeFactory = new InMemoryConfigurationStoreFactory(); baseDirectory = new File(contextProvider.getBaseDirectory(), "repositories"); IOUtil.mkdirs(baseDirectory); @@ -106,7 +100,7 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { } } - private Repository createRepository() throws RepositoryException, IOException { + private Repository createRepository() throws RepositoryException { Repository repository = RepositoryTestData.createHeartOfGold(); handler.create(repository); @@ -120,7 +114,6 @@ public abstract class SimpleRepositoryHandlerTestBase extends AbstractTestBase { return repository; } - protected File baseDirectory; private RepositoryHandler handler; diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java index f336bbb6e7..4d8fba1c26 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java @@ -543,7 +543,8 @@ public class RepositoryImportResource try { - repository = new Repository(null, type, name); + // TODO #8783 +// repository = new Repository(null, type, name); manager.create(repository); } catch (RepositoryAlreadyExistsException ex) @@ -738,7 +739,8 @@ public class RepositoryImportResource { for (String repositoryName : repositoryNames) { - Repository repository = manager.get(type, repositoryName); + // TODO #8783 + Repository repository = null; //manager.get(type, repositoryName); if (repository != null) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java index bf974b2fa4..5c9618860b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java @@ -561,42 +561,6 @@ public class RepositoryResource extends AbstractManagerResource 0) { + String type = uri.substring(0, typeSeparator); + + uri = uri.substring(typeSeparator + 1); + repository = getFromTypeAndUri(type, uri); + } + + return repository; + } + + private Repository getFromTypeAndUri(String type, String uri) { if (Strings.isNullOrEmpty(type)) { throw new ArgumentIsInvalidException("argument type is required"); } @@ -362,27 +395,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { return repository; } - @Override - public Repository getFromUri(String uri) { - AssertUtil.assertIsNotEmpty(uri); - - if (uri.startsWith(HttpUtil.SEPARATOR_PATH)) { - uri = uri.substring(1); - } - - int typeSeperator = uri.indexOf(HttpUtil.SEPARATOR_PATH); - Repository repository = null; - - if (typeSeperator > 0) { - String type = uri.substring(0, typeSeperator); - - uri = uri.substring(typeSeperator + 1); - repository = getFromTypeAndUri(type, uri); - } - - return repository; - } - @Override public RepositoryHandler getHandler(String type) { return handlerMap.get(type); diff --git a/scm-webapp/src/main/java/sonia/scm/repository/RepositoryMatcher.java b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryMatcher.java index d0ad30e84f..8cb3871047 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/RepositoryMatcher.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryMatcher.java @@ -32,14 +32,15 @@ package sonia.scm.repository; -import com.google.common.collect.Maps; -import java.util.Map; -import java.util.Set; -import javax.inject.Inject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import sonia.scm.util.HttpUtil; -import sonia.scm.util.Util; + import com.google.common.collect.Maps; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import sonia.scm.util.HttpUtil; + import sonia.scm.util.Util; + + import javax.inject.Inject; + import java.util.Map; + import java.util.Set; /** * RepositoryMatcher is able to check if a repository matches the requested path. @@ -83,9 +84,24 @@ public final class RepositoryMatcher { } private boolean isPathMatching(Repository repository, String path) { - return getPathMatcherForType(repository.getType()).isPathMatching(repository, path); + + String namespace = extractNamespace(path); + String remainingPath = path.substring(namespace.length() + 1); + + return getPathMatcherForType(repository.getType()).isPathMatching(repository, remainingPath); } - + + private String extractNamespace(String path) { + if (path.startsWith(HttpUtil.SEPARATOR_PATH)) { + path = path.substring(1); + } + int namespaceSeparator = path.indexOf(HttpUtil.SEPARATOR_PATH); + if (namespaceSeparator > 0) { + return path.substring(0, namespaceSeparator); + } + throw new IllegalArgumentException("no namespace in path " + path); + } + private RepositoryPathMatcher getPathMatcherForType(String type) { RepositoryPathMatcher pathMatcher = pathMatchers.get(type); if (pathMatcher == null) { diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java index 81059c1247..665c54c9e1 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java @@ -34,7 +34,11 @@ import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; import com.google.inject.Provider; import org.apache.shiro.SecurityUtils; -import org.apache.shiro.authc.*; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.mgt.DefaultSecurityManager; @@ -57,7 +61,12 @@ import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.security.KeyGenerator; import sonia.scm.user.UserTestData; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import static org.mockito.Mockito.mock; @@ -174,8 +183,8 @@ private long calculateAverage(List times) { when(repositoryDAO.getAll()).thenReturn(repositories.values()); } - private Repository createTestRepository(int number){ - Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "repo-" + number); + private Repository createTestRepository(int number) { + Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "namespace", "repo-" + number); repository.getPermissions().add(new Permission("trillian", PermissionType.READ)); return repository; } diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java index 7bbdd657ee..ae652dddf6 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -58,11 +58,26 @@ import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.JAXBConfigurationStoreFactory; import java.io.IOException; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.Stack; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; //~--- JDK imports ------------------------------------------------------------ @@ -84,12 +99,8 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase keys = new Stack<>(); @@ -320,12 +264,6 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase mockedNamespace); return new DefaultRepositoryManager(configuration, contextProvider, keyGenerator, repositoryDAO, handlerSet, createRepositoryMatcher(), namespaceStrategy); diff --git a/scm-webapp/src/test/java/sonia/scm/repository/RepositoryMatcherTest.java b/scm-webapp/src/test/java/sonia/scm/repository/RepositoryMatcherTest.java index c832bb8691..1d6371074c 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/RepositoryMatcherTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/RepositoryMatcherTest.java @@ -31,10 +31,13 @@ package sonia.scm.repository; import com.google.common.collect.Sets; -import java.util.Set; -import org.junit.Test; -import static org.junit.Assert.*; import org.junit.Before; +import org.junit.Test; + +import java.util.Set; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * Unit tests for {@link RepositoryMatcher}. @@ -54,11 +57,11 @@ public class RepositoryMatcherTest { @Test public void testMatches() { - assertFalse(matcher.matches(repository("hg", "scm"), "hg", "scm-test/ka")); - assertFalse(matcher.matches(repository("git", "scm-test"), "hg", "scm-test")); + assertFalse(matcher.matches(repository("hg", "scm"), "hg", "namespace/scm-test/ka")); + assertFalse(matcher.matches(repository("git", "scm-test"), "hg", "namespace/scm-test")); - assertTrue(matcher.matches(repository("hg", "scm-test"), "hg", "scm-test/ka")); - assertTrue(matcher.matches(repository("hg", "scm-test"), "hg", "scm-test")); + assertTrue(matcher.matches(repository("hg", "scm-test"), "hg", "namespace/scm-test/ka")); + assertTrue(matcher.matches(repository("hg", "scm-test"), "hg", "namespace/scm-test")); } @Test @@ -68,7 +71,7 @@ public class RepositoryMatcherTest { } private Repository repository(String type, String name) { - return new Repository(type + "-" + name, type, name); + return new Repository(type + "-" + name, type, "namespace", name); } private static class AbcRepositoryPathMatcher implements RepositoryPathMatcher { From 4af1842ea3ae48b505c1ed25106fd02edc0cd2e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 10 Jul 2018 16:37:01 +0200 Subject: [PATCH 025/186] change password and username --- scm-ui/src/modules/login.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-ui/src/modules/login.js b/scm-ui/src/modules/login.js index 53e2049ec8..13df980e93 100644 --- a/scm-ui/src/modules/login.js +++ b/scm-ui/src/modules/login.js @@ -66,8 +66,8 @@ export function login(username: string, password: string) { var login_data = { cookie: true, grant_type: "password", - password: username, - username: password + username, + password, }; return function(dispatch) { dispatch(loginRequest()); From af0de44172a44fc8d03adb203fcd9af48a81baf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 10 Jul 2018 16:37:27 +0200 Subject: [PATCH 026/186] deleteuserbutton is only a component --- .../src/users/containers/DeleteUserButton.js | 48 ++++++++----------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/scm-ui/src/users/containers/DeleteUserButton.js b/scm-ui/src/users/containers/DeleteUserButton.js index 195e603ae8..c26dc56fb6 100644 --- a/scm-ui/src/users/containers/DeleteUserButton.js +++ b/scm-ui/src/users/containers/DeleteUserButton.js @@ -1,46 +1,36 @@ // @flow import React from "react"; -import { deleteUser } from '../modules/users'; -import {connect} from "react-redux"; type Props = { user: any, - deleteUser: (username: string) => void + deleteUser: (link: string) => void }; class DeleteUser extends React.Component { deleteUser = () => { - this.props.deleteUser(this.props.user.name); + this.props.deleteUser(this.props.user._links.delete.href); + }; + + if(deleteButtonClicked) { + let deleteButtonAsk =
You really want to remove this user?
+ } + + isDeletable = () => { + return this.props.user._links.delete; }; render() { - if(this.props.user._links.delete) { - return ( - - - ); + if (!this.isDeletable()) { + return; } + return ( + + + ); } } -const mapStateToProps = state => { - return { - users: state.users.users - }; -}; - -const mapDispatchToProps = dispatch => { - return { - deleteUser: (username: string) => { - dispatch(deleteUser(username)); - } - }; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(DeleteUser); +export default DeleteUser; From 56848400217b3044c0c4afcdd38b2c6a40108bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Tue, 10 Jul 2018 16:37:40 +0200 Subject: [PATCH 027/186] add tests for view of delete button --- scm-ui/package.json | 5 +- .../users/containers/DeleteUserButton.test.js | 54 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 scm-ui/src/users/containers/DeleteUserButton.test.js diff --git a/scm-ui/package.json b/scm-ui/package.json index c91b237aa5..84776009b5 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -32,7 +32,10 @@ } }, "devDependencies": { - "prettier": "^1.13.7" + "enzyme": "^3.3.0", + "enzyme-adapter-react-16": "^1.1.1", + "prettier": "^1.13.7", + "react-test-renderer": "^16.4.1" }, "babel": { "presets": [ diff --git a/scm-ui/src/users/containers/DeleteUserButton.test.js b/scm-ui/src/users/containers/DeleteUserButton.test.js new file mode 100644 index 0000000000..efda995c86 --- /dev/null +++ b/scm-ui/src/users/containers/DeleteUserButton.test.js @@ -0,0 +1,54 @@ +import React from 'react'; +import {configure, shallow} from 'enzyme'; +import DeleteUserButton from "./DeleteUserButton"; +import Adapter from 'enzyme-adapter-react-16'; + +import 'raf/polyfill'; + +configure({ adapter: new Adapter() }); + +it('should render nothing, if the delete link is missing', () => { + + const user = { + _links: {} + }; + + const button = shallow(); + expect(button.text()).toBe(""); +}); + +it('should render the button', () => { + + const user = { + _links: { + "delete": { + "href": "/users" + } + } + }; + + const button = shallow(); + expect(button.text()).not.toBe(""); +}); + +it('should call the delete user function with delete url', () => { + + const user = { + _links: { + "delete": { + "href": "/users" + } + } + }; + + let calledUrl = null; + + function capture(url) { + calledUrl = url; + } + + const button = shallow(); + button.simulate("click"); + + expect(calledUrl).toBe("/users"); +}); From eb99eab354a43ce5acfd368d5fcd6f288eb44511 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Tue, 10 Jul 2018 16:52:23 +0200 Subject: [PATCH 028/186] Use apiClient --- scm-ui/src/containers/Login.js | 24 +++++++++--- scm-ui/src/modules/login.js | 52 +++++++++----------------- scm-ui/src/users/containers/UserRow.js | 6 +-- scm-ui/src/users/modules/users.js | 29 +++++++------- 4 files changed, 51 insertions(+), 60 deletions(-) diff --git a/scm-ui/src/containers/Login.js b/scm-ui/src/containers/Login.js index e811927831..d7c3e0fba7 100644 --- a/scm-ui/src/containers/Login.js +++ b/scm-ui/src/containers/Login.js @@ -16,17 +16,31 @@ const styles = { } }; -class Login extends React.Component { - state = {}; - handleUsernameChange(event) { +type Props = { + classes: any, + login: (username: string, password: string) => void +}; + +type State = { + username: string, + password: string +}; + +class Login extends React.Component { + constructor(props: Props) { + super(props); + this.state = { username: "", password: "" }; + } + + handleUsernameChange(event: SyntheticInputEvent) { this.setState({ username: event.target.value }); } - handlePasswordChange(event) { + handlePasswordChange(event: SyntheticInputEvent) { this.setState({ password: event.target.value }); } - handleSubmit(event) { + handleSubmit(event: Event) { event.preventDefault(); this.props.login(this.state.username, this.state.password); } diff --git a/scm-ui/src/modules/login.js b/scm-ui/src/modules/login.js index 53e2049ec8..d3fc8c64e2 100644 --- a/scm-ui/src/modules/login.js +++ b/scm-ui/src/modules/login.js @@ -1,7 +1,10 @@ //@flow -const LOGIN_URL = "/scm/api/rest/v2/auth/access_token"; -const AUTHENTICATION_INFO_URL = "/scm/api/rest/v2/me"; +import { apiClient } from "../apiclient"; +import { isRegExp } from "util"; + +const LOGIN_URL = "/auth/access_token"; +const AUTHENTICATION_INFO_URL = "/me"; export const LOGIN = "scm/auth/login"; export const LOGIN_REQUEST = "scm/auth/login_request"; @@ -19,21 +22,12 @@ export function getIsAuthenticatedRequest() { } export function getIsAuthenticated() { - return function(dispatch) { + return function(dispatch: any) { dispatch(getIsAuthenticatedRequest()); - - return fetch(AUTHENTICATION_INFO_URL, { - credentials: "same-origin", - headers: { - Cache: "no-cache" - } - }) + return apiClient + .get(AUTHENTICATION_INFO_URL) .then(response => { - if (response.ok) { - return response.json(); - } else { - dispatch(isNotAuthenticated()); - } + return response.json(); }) .then(data => { if (data) { @@ -66,27 +60,17 @@ export function login(username: string, password: string) { var login_data = { cookie: true, grant_type: "password", - password: username, - username: password + username, + password }; - return function(dispatch) { + return function(dispatch: any) { dispatch(loginRequest()); - return fetch(LOGIN_URL, { - method: "POST", - headers: { - "Content-Type": "application/json; charset=utf-8" - }, - credentials: "same-origin", - body: JSON.stringify(login_data) - }).then( - response => { - if (response.ok) { - dispatch(getIsAuthenticated()); - dispatch(loginSuccessful()); - } - }, - error => console.log("error logging in: " + error) - ); + return apiClient.post(LOGIN_URL, login_data).then(response => { + if (response.ok) { + dispatch(getIsAuthenticated()); + dispatch(loginSuccessful()); + } + }); }; } diff --git a/scm-ui/src/users/containers/UserRow.js b/scm-ui/src/users/containers/UserRow.js index 077ffa7fc3..5431178ba9 100644 --- a/scm-ui/src/users/containers/UserRow.js +++ b/scm-ui/src/users/containers/UserRow.js @@ -6,10 +6,7 @@ type Props = { user: any }; - - export default class UserRow extends React.Component { - render() { return ( @@ -19,10 +16,9 @@ export default class UserRow extends React.Component { - + - ); } } diff --git a/scm-ui/src/users/modules/users.js b/scm-ui/src/users/modules/users.js index a2a157d1c9..1fdc499535 100644 --- a/scm-ui/src/users/modules/users.js +++ b/scm-ui/src/users/modules/users.js @@ -1,10 +1,10 @@ // @flow -import {apiClient, PAGE_NOT_FOUND_ERROR} from '../../apiclient'; +import { apiClient, PAGE_NOT_FOUND_ERROR } from "../../apiclient"; const FETCH_USERS = "scm/users/FETCH"; const FETCH_USERS_SUCCESS = "scm/users/FETCH_SUCCESS"; const FETCH_USERS_FAILURE = "scm/users/FETCH_FAILURE"; -const FETCH_USERS_NOTFOUND = 'scm/users/FETCH_NOTFOUND'; +const FETCH_USERS_NOTFOUND = "scm/users/FETCH_NOTFOUND"; const DELETE_USER = "scm/users/DELETE"; const DELETE_USER_SUCCESS = "scm/users/DELETE_SUCCESS"; @@ -34,10 +34,10 @@ function usersNotFound(url: string) { } export function fetchUsers() { - - return function(dispatch) { + return function(dispatch: any => void) { dispatch(requestUsers()); - return apiClient.get(USERS_URL) + return apiClient + .get(USERS_URL) .then(response => { return response; }) @@ -49,14 +49,14 @@ export function fetchUsers() { .then(data => { dispatch(fetchUsersSuccess(data)); }) - .catch((err) => { + .catch(err => { if (err === PAGE_NOT_FOUND_ERROR) { dispatch(usersNotFound(USERS_URL)); } else { dispatch(failedToFetchUsers(USERS_URL, err)); } }); - } + }; } function fetchUsersSuccess(users: any) { @@ -67,7 +67,7 @@ function fetchUsersSuccess(users: any) { } export function shouldFetchUsers(state: any): boolean { - if(state.users.users == null){ + if (state.users.users == null) { return true; } return false; @@ -81,8 +81,6 @@ export function fetchUsersIfNeeded() { }; } - - function requestDeleteUser(url: string) { return { type: DELETE_USER, @@ -92,7 +90,7 @@ function requestDeleteUser(url: string) { function deleteUserSuccess() { return { - type: DELETE_USER_SUCCESS, + type: DELETE_USER_SUCCESS }; } @@ -107,16 +105,15 @@ function deleteUserFailure(url: string, err: Error) { export function deleteUser(username: string) { return function(dispatch) { dispatch(requestDeleteUser(username)); - return apiClient.delete(USERS_URL + '/' + username) + return apiClient + .delete(USERS_URL + "/" + username) .then(() => { dispatch(deleteUserSuccess()); }) - .catch((err) => dispatch(deleteUserFailure(username, err))); - } + .catch(err => dispatch(deleteUserFailure(username, err))); + }; } - - export default function reducer(state: any = {}, action: any = {}) { switch (action.type) { case FETCH_USERS: From 5db74e08cbf3fdc64df56d25ae65eb07dad8d362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 11 Jul 2018 08:08:46 +0200 Subject: [PATCH 029/186] Fix git directory name --- .../sonia/scm/web/GitRepositoryResolver.java | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java index 2ddf4b3de9..76e742a71a 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java @@ -36,8 +36,8 @@ package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import com.google.inject.Inject; - import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache; @@ -46,19 +46,17 @@ import org.eclipse.jgit.transport.resolver.RepositoryResolver; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; import org.eclipse.jgit.util.FS; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.repository.RepositoryProvider; -//~--- JDK imports ------------------------------------------------------------ - +import javax.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; -import javax.servlet.http.HttpServletRequest; +//~--- JDK imports ------------------------------------------------------------ /** * @@ -72,17 +70,11 @@ public class GitRepositoryResolver implements RepositoryResolver Date: Wed, 11 Jul 2018 08:25:06 +0200 Subject: [PATCH 030/186] Correct sequence of setting and reading namespace for new repository --- .../repository/DefaultRepositoryManager.java | 7 +- .../DefaultRepositoryManagerTest.java | 73 ++++++++++--------- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java index 07bff4e999..c644034df9 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -130,9 +130,13 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { public void create(Repository repository, boolean initRepository) throws RepositoryException { - logger.info("create repository {} of type {}", repository.getNamespaceAndName(), repository.getType()); + logger.info("create repository {} of type {}", repository.getName(), repository.getType()); RepositoryPermissions.create().check(); + + repository.setNamespace(namespaceStrategy.getNamespace()); + logger.info("setting namespace of new repository {} to {}", repository.getName(), repository.getNamespace()); + AssertUtil.assertIsValid(repository); if (repositoryDAO.contains(repository.getNamespaceAndName())) { @@ -141,7 +145,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { repository.setId(keyGenerator.createKey()); repository.setCreationDate(System.currentTimeMillis()); - repository.setNamespace(namespaceStrategy.getNamespace()); if (initRepository) { getHandler(repository).create(repository); diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java index ae652dddf6..e76d2af7b3 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -57,7 +57,6 @@ import sonia.scm.security.KeyGenerator; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.JAXBConfigurationStoreFactory; -import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -102,7 +101,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase keys = new Stack<>(); @@ -265,7 +264,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBaseemptySet()); } - private Repository createRepository(Repository repository) throws RepositoryException, IOException { + private Repository createRepository(Repository repository) throws RepositoryException { manager.create(repository); assertNotNull(repository.getId()); assertNotNull(manager.get(repository.getId())); @@ -538,17 +545,17 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase manager, Repository repository) - throws RepositoryException, IOException { + throws RepositoryException { String id = repository.getId(); From 8391e75a159b0452718f70d28fc9f0f83afe58e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 11 Jul 2018 10:14:00 +0200 Subject: [PATCH 031/186] Correct servlet paths for svn requests --- .../src/main/java/sonia/scm/web/SvnDAVServlet.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnDAVServlet.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnDAVServlet.java index db22857595..c811179255 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnDAVServlet.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnDAVServlet.java @@ -37,13 +37,10 @@ package sonia.scm.web; import com.google.inject.Inject; import com.google.inject.Singleton; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.tmatesoft.svn.core.internal.server.dav.DAVConfig; import org.tmatesoft.svn.core.internal.server.dav.DAVServlet; - import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryProvider; import sonia.scm.repository.RepositoryRequestListenerUtil; @@ -51,14 +48,13 @@ import sonia.scm.repository.SvnRepositoryHandler; import sonia.scm.util.AssertUtil; import sonia.scm.util.HttpUtil; -//~--- JDK imports ------------------------------------------------------------ - -import java.io.IOException; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +//~--- JDK imports ------------------------------------------------------------ /** * @@ -224,7 +220,7 @@ public class SvnDAVServlet extends DAVServlet pathInfo = pathInfo.substring(1); } - pathInfo = pathInfo.substring(repository.getName().length()); + pathInfo = pathInfo.substring(repository.getNamespace().length() + 1 + repository.getName().length()); } return pathInfo; @@ -249,7 +245,7 @@ public class SvnDAVServlet extends DAVServlet servletPath = servletPath.concat(HttpUtil.SEPARATOR_PATH); } - servletPath = servletPath.concat(repository.getName()); + servletPath = servletPath + repository.getNamespace() + "/" + repository.getName(); } return servletPath; From e3caa93aa72895f66ccf0873563d090aeda0b1c7 Mon Sep 17 00:00:00 2001 From: Philipp Czora Date: Wed, 11 Jul 2018 12:02:53 +0200 Subject: [PATCH 032/186] Improved flow coverage, fixed bugs and enabled deleting users --- scm-ui/package.json | 3 +- scm-ui/src/apiclient.js | 16 +++++-- scm-ui/src/containers/App.js | 16 ++++--- scm-ui/src/modules/login.js | 25 ++++++++--- scm-ui/src/types/hal.js | 6 +++ .../src/users/containers/DeleteUserButton.js | 9 +--- .../users/containers/DeleteUserButton.test.js | 31 ++++++------- scm-ui/src/users/containers/UserRow.js | 14 +++--- scm-ui/src/users/containers/Users.js | 34 +++++++------- scm-ui/src/users/modules/users.js | 45 +++++++------------ scm-ui/src/users/types/User.js | 10 +++++ 11 files changed, 118 insertions(+), 91 deletions(-) create mode 100644 scm-ui/src/types/hal.js create mode 100644 scm-ui/src/users/types/User.js diff --git a/scm-ui/package.json b/scm-ui/package.json index 84776009b5..5c11dc9813 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -5,7 +5,6 @@ "private": true, "dependencies": { "classnames": "^2.2.5", - "flow-bin": "^0.75.0", "history": "^4.7.2", "react": "^16.4.1", "react-dom": "^16.4.1", @@ -34,6 +33,8 @@ "devDependencies": { "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", + "flow-bin": "^0.75.0", + "flow-typed": "^2.5.1", "prettier": "^1.13.7", "react-test-renderer": "^16.4.1" }, diff --git a/scm-ui/src/apiclient.js b/scm-ui/src/apiclient.js index 02d3263fec..0b945d1fbd 100644 --- a/scm-ui/src/apiclient.js +++ b/scm-ui/src/apiclient.js @@ -4,6 +4,7 @@ const apiUrl = process.env.API_URL || process.env.PUBLIC_URL || "/scm"; export const PAGE_NOT_FOUND_ERROR = Error("page not found"); +export const NOT_AUTHENTICATED_ERROR = Error("not authenticated"); const fetchOptions: RequestOptions = { credentials: "same-origin", @@ -15,7 +16,7 @@ const fetchOptions: RequestOptions = { function handleStatusCode(response: Response) { if (!response.ok) { if (response.status === 401) { - return response; + throw NOT_AUTHENTICATED_ERROR; } if (response.status === 404) { throw PAGE_NOT_FOUND_ERROR; @@ -26,11 +27,14 @@ function handleStatusCode(response: Response) { } function createUrl(url: string) { + if (url.indexOf("://") > 0) { + return url; + } return `${apiUrl}/api/rest/v2/${url}`; } class ApiClient { - get(url: string) { + get(url: string): Promise { return fetch(createUrl(url), fetchOptions).then(handleStatusCode); } @@ -38,7 +42,7 @@ class ApiClient { return this.httpRequestWithJSONBody(url, payload, "POST"); } - delete(url: string) { + delete(url: string): Promise { let options: RequestOptions = { method: "DELETE" }; @@ -46,7 +50,11 @@ class ApiClient { return fetch(createUrl(url), options).then(handleStatusCode); } - httpRequestWithJSONBody(url: string, payload: any, method: string) { + httpRequestWithJSONBody( + url: string, + payload: any, + method: string + ): Promise { let options: RequestOptions = { method: method, body: JSON.stringify(payload) diff --git a/scm-ui/src/containers/App.js b/scm-ui/src/containers/App.js index 1ee8df1c98..aa40fdc4a9 100644 --- a/scm-ui/src/containers/App.js +++ b/scm-ui/src/containers/App.js @@ -9,17 +9,19 @@ import { withRouter } from "react-router-dom"; type Props = { login: boolean, username: string, - getAuthState: any + getAuthState: () => void, + loading: boolean }; -class App extends Component { - componentWillMount() { +class App extends Component { + componentDidMount() { this.props.getAuthState(); } render() { - const { login, username } = this.props.login; - - if (!login) { + const { login, username, loading } = this.props; + if (loading) { + return
Loading...
; + } else if (!login) { return (
@@ -44,7 +46,7 @@ const mapDispatchToProps = dispatch => { }; const mapStateToProps = state => { - return { login: state.login }; + return state.login || {}; }; export default withRouter( diff --git a/scm-ui/src/modules/login.js b/scm-ui/src/modules/login.js index b4f505296d..c2bced3891 100644 --- a/scm-ui/src/modules/login.js +++ b/scm-ui/src/modules/login.js @@ -1,6 +1,6 @@ //@flow -import { apiClient } from "../apiclient"; +import { apiClient, NOT_AUTHENTICATED_ERROR } from "../apiclient"; const LOGIN_URL = "/auth/access_token"; const AUTHENTICATION_INFO_URL = "/me"; @@ -21,7 +21,7 @@ export function getIsAuthenticatedRequest() { } export function getIsAuthenticated() { - return function(dispatch: (any) => void) { + return function(dispatch: any => void) { dispatch(getIsAuthenticatedRequest()); return apiClient .get(AUTHENTICATION_INFO_URL) @@ -32,6 +32,13 @@ export function getIsAuthenticated() { if (data) { dispatch(isAuthenticated(data.username)); } + }) + .catch((error: Error) => { + if (error === NOT_AUTHENTICATED_ERROR) { + dispatch(isNotAuthenticated()); + } else { + // TODO: Handle errors other than not_authenticated + } }); }; } @@ -60,9 +67,9 @@ export function login(username: string, password: string) { cookie: true, grant_type: "password", username, - password, + password }; - return function(dispatch: (any) => void) { + return function(dispatch: any => void) { dispatch(loginRequest()); return apiClient.post(LOGIN_URL, login_data).then(response => { if (response.ok) { @@ -79,23 +86,29 @@ export function loginSuccessful() { }; } -export default function reducer(state: any = {}, action: any = {}) { +export default function reducer( + state: any = { loading: true }, + action: any = {} +) { switch (action.type) { case LOGIN: return { ...state, + loading: true, login: false, error: null }; case LOGIN_SUCCESSFUL: return { ...state, + loading: false, login: true, error: null }; case LOGIN_FAILED: return { ...state, + loading: false, login: false, error: action.payload }; @@ -103,12 +116,14 @@ export default function reducer(state: any = {}, action: any = {}) { return { ...state, login: true, + loading: false, username: action.username }; case IS_NOT_AUTHENTICATED: return { ...state, login: false, + loading: false, username: null, error: null }; diff --git a/scm-ui/src/types/hal.js b/scm-ui/src/types/hal.js new file mode 100644 index 0000000000..4c68bbab93 --- /dev/null +++ b/scm-ui/src/types/hal.js @@ -0,0 +1,6 @@ +// @flow +export type Link = { + href: string +}; + +export type Links = { [string]: Link }; diff --git a/scm-ui/src/users/containers/DeleteUserButton.js b/scm-ui/src/users/containers/DeleteUserButton.js index c26dc56fb6..15e14bc7f6 100644 --- a/scm-ui/src/users/containers/DeleteUserButton.js +++ b/scm-ui/src/users/containers/DeleteUserButton.js @@ -1,21 +1,17 @@ // @flow import React from "react"; +import type { User } from "../types/User"; type Props = { - user: any, + user: User, deleteUser: (link: string) => void }; class DeleteUser extends React.Component { - deleteUser = () => { this.props.deleteUser(this.props.user._links.delete.href); }; - if(deleteButtonClicked) { - let deleteButtonAsk =
You really want to remove this user?
- } - isDeletable = () => { return this.props.user._links.delete; }; @@ -28,7 +24,6 @@ class DeleteUser extends React.Component { - ); } } diff --git a/scm-ui/src/users/containers/DeleteUserButton.test.js b/scm-ui/src/users/containers/DeleteUserButton.test.js index efda995c86..4e1a1af663 100644 --- a/scm-ui/src/users/containers/DeleteUserButton.test.js +++ b/scm-ui/src/users/containers/DeleteUserButton.test.js @@ -1,42 +1,39 @@ -import React from 'react'; -import {configure, shallow} from 'enzyme'; +import React from "react"; +import { configure, shallow } from "enzyme"; import DeleteUserButton from "./DeleteUserButton"; -import Adapter from 'enzyme-adapter-react-16'; +import Adapter from "enzyme-adapter-react-16"; -import 'raf/polyfill'; +import "raf/polyfill"; configure({ adapter: new Adapter() }); -it('should render nothing, if the delete link is missing', () => { - +it("should render nothing, if the delete link is missing", () => { const user = { _links: {} }; - const button = shallow(); + const button = shallow(); expect(button.text()).toBe(""); }); -it('should render the button', () => { - +it("should render the button", () => { const user = { _links: { - "delete": { - "href": "/users" + delete: { + href: "/users" } } }; - const button = shallow(); + const button = shallow(); expect(button.text()).not.toBe(""); }); -it('should call the delete user function with delete url', () => { - +it("should call the delete user function with delete url", () => { const user = { _links: { - "delete": { - "href": "/users" + delete: { + href: "/users" } } }; @@ -47,7 +44,7 @@ it('should call the delete user function with delete url', () => { calledUrl = url; } - const button = shallow(); + const button = shallow(); button.simulate("click"); expect(calledUrl).toBe("/users"); diff --git a/scm-ui/src/users/containers/UserRow.js b/scm-ui/src/users/containers/UserRow.js index 5431178ba9..2f2dba001b 100644 --- a/scm-ui/src/users/containers/UserRow.js +++ b/scm-ui/src/users/containers/UserRow.js @@ -1,22 +1,26 @@ // @flow import React from "react"; import DeleteUserButton from "./DeleteUserButton"; +import type { User } from "../types/User"; type Props = { - user: any + user: User, + deleteUser: string => void }; export default class UserRow extends React.Component { render() { + const { user, deleteUser } = this.props; return ( - {this.props.user.displayName} - {this.props.user.mail} + {user.name} + {user.displayName} + {user.mail} - + - + ); diff --git a/scm-ui/src/users/containers/Users.js b/scm-ui/src/users/containers/Users.js index 0702a56245..9c9914e6dd 100644 --- a/scm-ui/src/users/containers/Users.js +++ b/scm-ui/src/users/containers/Users.js @@ -2,27 +2,22 @@ import React from "react"; import { connect } from "react-redux"; -import { fetchUsersIfNeeded, fetchUsers } from "../modules/users"; +import { fetchUsers, deleteUser } from "../modules/users"; import Login from "../../containers/Login"; import UserRow from "./UserRow"; +import type { User } from "../types/User"; type Props = { login: boolean, - error: any, - users: any, - fetchUsersIfNeeded: () => void, + error: Error, + users: Array, fetchUsers: () => void, - fetchUsersIfNeeded: (url: string) => void, - + deleteUser: string => void }; class Users extends React.Component { - componentWillMount() { - this.props.fetchUsersIfNeeded(); - } - - componentDidUpdate() { - this.props.fetchUsersIfNeeded(); + componentDidMount() { + this.props.fetchUsers(); } render() { @@ -35,13 +30,20 @@ class Users extends React.Component { Name + Display Name E-Mail Admin {this.props.users.map((user, index) => { - return ; + return ( + + ); })} @@ -61,11 +63,11 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { - fetchUsersIfNeeded: () => { - dispatch(fetchUsersIfNeeded()); - }, fetchUsers: () => { dispatch(fetchUsers()); + }, + deleteUser: (link: string) => { + dispatch(deleteUser(link)); } }; }; diff --git a/scm-ui/src/users/modules/users.js b/scm-ui/src/users/modules/users.js index 1fdc499535..14c4db1851 100644 --- a/scm-ui/src/users/modules/users.js +++ b/scm-ui/src/users/modules/users.js @@ -1,5 +1,6 @@ // @flow import { apiClient, PAGE_NOT_FOUND_ERROR } from "../../apiclient"; +import { ThunkDispatch } from "redux-thunk"; const FETCH_USERS = "scm/users/FETCH"; const FETCH_USERS_SUCCESS = "scm/users/FETCH_SUCCESS"; @@ -34,7 +35,7 @@ function usersNotFound(url: string) { } export function fetchUsers() { - return function(dispatch: any => void) { + return function(dispatch: ThunkDispatch) { dispatch(requestUsers()); return apiClient .get(USERS_URL) @@ -66,21 +67,6 @@ function fetchUsersSuccess(users: any) { }; } -export function shouldFetchUsers(state: any): boolean { - if (state.users.users == null) { - return true; - } - return false; -} - -export function fetchUsersIfNeeded() { - return (dispatch, getState) => { - if (shouldFetchUsers(getState())) { - dispatch(fetchUsers()); - } - }; -} - function requestDeleteUser(url: string) { return { type: DELETE_USER, @@ -102,41 +88,42 @@ function deleteUserFailure(url: string, err: Error) { }; } -export function deleteUser(username: string) { - return function(dispatch) { - dispatch(requestDeleteUser(username)); +export function deleteUser(link: string) { + return function(dispatch: ThunkDispatch) { + dispatch(requestDeleteUser(link)); return apiClient - .delete(USERS_URL + "/" + username) + .delete(link) .then(() => { dispatch(deleteUserSuccess()); + dispatch(fetchUsers()); }) - .catch(err => dispatch(deleteUserFailure(username, err))); + .catch(err => dispatch(deleteUserFailure(link, err))); }; } export default function reducer(state: any = {}, action: any = {}) { switch (action.type) { case FETCH_USERS: + case DELETE_USER: return { ...state, - users: null + users: null, + loading: true }; case FETCH_USERS_SUCCESS: return { ...state, error: null, - users: action.payload._embedded.users + users: action.payload._embedded.users, + loading: false }; case FETCH_USERS_FAILURE: + case DELETE_USER_FAILURE: return { ...state, login: false, - error: action.payload - }; - case DELETE_USER_SUCCESS: - return { - ...state, - users: null + error: action.payload, + loading: false }; default: diff --git a/scm-ui/src/users/types/User.js b/scm-ui/src/users/types/User.js new file mode 100644 index 0000000000..9cf4e79728 --- /dev/null +++ b/scm-ui/src/users/types/User.js @@ -0,0 +1,10 @@ +//@flow +import type { Link, Links } from "../../types/hal"; + +export type User = { + displayName: string, + name: string, + mail: string, + admin: boolean, + _links: Links +}; From 53f3264f6e77afc71402dfe3cce77e287f90c9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 11 Jul 2018 12:53:48 +0200 Subject: [PATCH 033/186] Correct repository name for hg --- .../main/java/sonia/scm/web/HgCGIServlet.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java index e00304c977..6cb4d523f0 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java @@ -39,10 +39,8 @@ import com.google.common.base.Stopwatch; import com.google.common.base.Strings; import com.google.inject.Inject; import com.google.inject.Singleton; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.SCMContext; import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.HgConfig; @@ -60,19 +58,17 @@ 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.Base64; - -import java.util.Enumeration; - 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; + +//~--- JDK imports ------------------------------------------------------------ /** * @@ -305,7 +301,7 @@ public class HgCGIServlet extends HttpServlet executor.setExceptionHandler(exceptionHandler); executor.setStatusCodeHandler(exceptionHandler); executor.setContentLengthWorkaround(true); - executor.getEnvironment().set(ENV_REPOSITORY_NAME, name); + executor.getEnvironment().set(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName()); executor.getEnvironment().set(ENV_REPOSITORY_PATH, directory.getAbsolutePath()); From d35a56e07e9a071c50143d9c5f600303a4a04e2f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 11 Jul 2018 14:59:01 +0200 Subject: [PATCH 034/186] initial styling --- scm-ui/package.json | 11 +- scm-ui/public/favicon.ico | Bin 3870 -> 1150 bytes scm-ui/public/index.html | 2 +- scm-ui/public/manifest.json | 4 +- scm-ui/src/components/Header.js | 31 + scm-ui/src/components/InputField.js | 49 + scm-ui/src/components/Logo.js | 11 + scm-ui/src/components/PrimaryNavigation.js | 17 + .../src/components/PrimaryNavigationLink.js | 29 + scm-ui/src/components/SubmitButton.js | 38 + scm-ui/src/containers/App.css | 6435 +++++++++++++++++ scm-ui/src/containers/App.js | 35 +- scm-ui/src/containers/App.scss | 30 + scm-ui/src/containers/Login.js | 88 +- scm-ui/src/containers/Main.js | 26 +- scm-ui/src/containers/Navigation.js | 52 - scm-ui/src/images/blib.jpg | Bin 0 -> 20857 bytes scm-ui/src/images/logo.png | Bin 0 -> 15162 bytes 18 files changed, 6742 insertions(+), 116 deletions(-) create mode 100644 scm-ui/src/components/Header.js create mode 100644 scm-ui/src/components/InputField.js create mode 100644 scm-ui/src/components/Logo.js create mode 100644 scm-ui/src/components/PrimaryNavigation.js create mode 100644 scm-ui/src/components/PrimaryNavigationLink.js create mode 100644 scm-ui/src/components/SubmitButton.js create mode 100644 scm-ui/src/containers/App.css create mode 100644 scm-ui/src/containers/App.scss delete mode 100644 scm-ui/src/containers/Navigation.js create mode 100644 scm-ui/src/images/blib.jpg create mode 100644 scm-ui/src/images/logo.png diff --git a/scm-ui/package.json b/scm-ui/package.json index 5c11dc9813..c482349665 100644 --- a/scm-ui/package.json +++ b/scm-ui/package.json @@ -4,6 +4,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "bulma": "^0.7.1", "classnames": "^2.2.5", "history": "^4.7.2", "react": "^16.4.1", @@ -19,8 +20,12 @@ "redux-thunk": "^2.3.0" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", + "build-css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/", + "watch-css": "npm run build-css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive", + "start-js": "react-scripts start", + "start": "npm-run-all -p watch-css start-js", + "build-js": "react-scripts build", + "build": "npm-run-all build-css build-js", "test": "jest", "eject": "react-scripts eject", "flow": "flow" @@ -35,6 +40,8 @@ "enzyme-adapter-react-16": "^1.1.1", "flow-bin": "^0.75.0", "flow-typed": "^2.5.1", + "node-sass-chokidar": "^1.3.0", + "npm-run-all": "^4.1.3", "prettier": "^1.13.7", "react-test-renderer": "^16.4.1" }, diff --git a/scm-ui/public/favicon.ico b/scm-ui/public/favicon.ico index a11777cc471a4344702741ab1c8a588998b1311a..e5803f340df8c825ba1344e189e192e34f983a9f 100644 GIT binary patch literal 1150 zcmcJNJ4?e*6vwY1D7X{^7l+0sX#^39F22Bg1N#x&baZzx;NY0i`oM@*P<$hbqBYf_ z;2>GF)!Le>^_33!01nsxX-#nuYX)!m_1xZbe&-(2Fjj|8Ai(e~u+e(PS{P&P07p2f zoFgz5x#p${^!vY7DLEMR+dGv?MSwtF$sCN9P6t%l`&sa}sUG*MPNLuP<5Der6O?Cn z5`FyrsN3+<1FivV5O5N`_S<0>f8Fo=bB`~dBkImckNJ8K;V*j~{CP()i<_cQYZiDk zKl%LOc*rQby9zIEN$%5n$Sth4Y4QR1&YB^pW zjsU#5$FQ$H6R+mCh!15P2(S&u?z4Hil8t7f{j9<0TCA`!Pz8ZlgLeh L1`y;UtsLM0i literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= z!sii`1giyH7EKL_+Wi0ab<)&E_0KD!3Rp2^HNB*K2@PHCs4PWSA32*-^7d{9nH2_E zmC{C*N*)(vEF1_aMamw2A{ZH5aIDqiabnFdJ|y0%aS|64E$`s2ccV~3lR!u<){eS` z#^Mx6o(iP1Ix%4dv`t@!&Za-K@mTm#vadc{0aWDV*_%EiGK7qMC_(`exc>-$Gb9~W!w_^{*pYRm~G zBN{nA;cm^w$VWg1O^^<6vY`1XCD|s_zv*g*5&V#wv&s#h$xlUilPe4U@I&UXZbL z0)%9Uj&@yd03n;!7do+bfixH^FeZ-Ema}s;DQX2gY+7g0s(9;`8GyvPY1*vxiF&|w z>!vA~GA<~JUqH}d;DfBSi^IT*#lrzXl$fNpq0_T1tA+`A$1?(gLb?e#0>UELvljtQ zK+*74m0jn&)5yk8mLBv;=@}c{t0ztT<v;Avck$S6D`Z)^c0(jiwKhQsn|LDRY&w(Fmi91I7H6S;b0XM{e zXp0~(T@k_r-!jkLwd1_Vre^v$G4|kh4}=Gi?$AaJ)3I+^m|Zyj#*?Kp@w(lQdJZf4 z#|IJW5z+S^e9@(6hW6N~{pj8|NO*>1)E=%?nNUAkmv~OY&ZV;m-%?pQ_11)hAr0oAwILrlsGawpxx4D43J&K=n+p3WLnlDsQ$b(9+4 z?mO^hmV^F8MV{4Lx>(Q=aHhQ1){0d*(e&s%G=i5rq3;t{JC zmgbn5Nkl)t@fPH$v;af26lyhH!k+#}_&aBK4baYPbZy$5aFx4}ka&qxl z$=Rh$W;U)>-=S-0=?7FH9dUAd2(q#4TCAHky!$^~;Dz^j|8_wuKc*YzfdAht@Q&ror?91Dm!N03=4=O!a)I*0q~p0g$Fm$pmr$ zb;wD;STDIi$@M%y1>p&_>%?UP($15gou_ue1u0!4(%81;qcIW8NyxFEvXpiJ|H4wz z*mFT(qVx1FKufG11hByuX%lPk4t#WZ{>8ka2efjY`~;AL6vWyQKpJun2nRiZYDij$ zP>4jQXPaP$UC$yIVgGa)jDV;F0l^n(V=HMRB5)20V7&r$jmk{UUIe zVjKroK}JAbD>B`2cwNQ&GDLx8{pg`7hbA~grk|W6LgiZ`8y`{Iq0i>t!3p2}MS6S+ zO_ruKyAElt)rdS>CtF7j{&6rP-#c=7evGMt7B6`7HG|-(WL`bDUAjyn+k$mx$CH;q2Dz4x;cPP$hW=`pFfLO)!jaCL@V2+F)So3}vg|%O*^T1j>C2lx zsURO-zIJC$^$g2byVbRIo^w>UxK}74^TqUiRR#7s_X$e)$6iYG1(PcW7un-va-S&u zHk9-6Zn&>T==A)lM^D~bk{&rFzCi35>UR!ZjQkdSiNX*-;l4z9j*7|q`TBl~Au`5& z+c)*8?#-tgUR$Zd%Q3bs96w6k7q@#tUn`5rj+r@_sAVVLqco|6O{ILX&U-&-cbVa3 zY?ngHR@%l{;`ri%H*0EhBWrGjv!LE4db?HEWb5mu*t@{kv|XwK8?npOshmzf=vZA@ zVSN9sL~!sn?r(AK)Q7Jk2(|M67Uy3I{eRy z_l&Y@A>;vjkWN5I2xvFFTLX0i+`{qz7C_@bo`ZUzDugfq4+>a3?1v%)O+YTd6@Ul7 zAfLfm=nhZ`)P~&v90$&UcF+yXm9sq!qCx3^9gzIcO|Y(js^Fj)Rvq>nQAHI92ap=P z10A4@prk+AGWCb`2)dQYFuR$|H6iDE8p}9a?#nV2}LBCoCf(Xi2@szia7#gY>b|l!-U`c}@ zLdhvQjc!BdLJvYvzzzngnw51yRYCqh4}$oRCy-z|v3Hc*d|?^Wj=l~18*E~*cR_kU z{XsxM1i{V*4GujHQ3DBpl2w4FgFR48Nma@HPgnyKoIEY-MqmMeY=I<%oG~l!f<+FN z1ZY^;10j4M4#HYXP zw5eJpA_y(>uLQ~OucgxDLuf}fVs272FaMxhn4xnDGIyLXnw>Xsd^J8XhcWIwIoQ9} z%FoSJTAGW(SRGwJwb=@pY7r$uQRK3Zd~XbxU)ts!4XsJrCycrWSI?e!IqwqIR8+Jh zlRjZ`UO1I!BtJR_2~7AbkbSm%XQqxEPkz6BTGWx8e}nQ=w7bZ|eVP4?*Tb!$(R)iC z9)&%bS*u(lXqzitAN)Oo=&Ytn>%Hzjc<5liuPi>zC_nw;Z0AE3Y$Jao_Q90R-gl~5 z_xAb2J%eArrC1CN4G$}-zVvCqF1;H;abAu6G*+PDHSYFx@Tdbfox*uEd3}BUyYY-l zTfEsOqsi#f9^FoLO;ChK<554qkri&Av~SIM*{fEYRE?vH7pTAOmu2pz3X?Wn*!ROX ztd54huAk&mFBemMooL33RV-*1f0Q3_(7hl$<#*|WF9P!;r;4_+X~k~uKEqdzZ$5Al zV63XN@)j$FN#cCD;ek1R#l zv%pGrhB~KWgoCj%GT?%{@@o(AJGt*PG#l3i>lhmb_twKH^EYvacVY-6bsCl5*^~L0 zonm@lk2UvvTKr2RS%}T>^~EYqdL1q4nD%0n&Xqr^cK^`J5W;lRRB^R-O8b&HENO||mo0xaD+S=I8RTlIfVgqN@SXDr2&-)we--K7w= zJVU8?Z+7k9dy;s;^gDkQa`0nz6N{T?(A&Iz)2!DEecLyRa&FI!id#5Z7B*O2=PsR0 zEvc|8{NS^)!d)MDX(97Xw}m&kEO@5jqRaDZ!+%`wYOI<23q|&js`&o4xvjP7D_xv@ z5hEwpsp{HezI9!~6O{~)lLR@oF7?J7i>1|5a~UuoN=q&6N}EJPV_GD`&M*v8Y`^2j zKII*d_@Fi$+i*YEW+Hbzn{iQk~yP z>7N{S4)r*!NwQ`(qcN#8SRQsNK6>{)X12nbF`*7#ecO7I)Q$uZsV+xS4E7aUn+U(K baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ diff --git a/scm-ui/public/index.html b/scm-ui/public/index.html index ed0ebafa1b..1891f54ac7 100644 --- a/scm-ui/public/index.html +++ b/scm-ui/public/index.html @@ -19,7 +19,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + SCM-Manager