##// END OF EJS Templates
#227 Initial version of repository groups permissions system...
marcink -
r1982:87f0800a beta
parent child Browse files
Show More
@@ -0,0 +1,270 b''
1 <table id="permissions_manage" class="noborder">
2 <tr>
3 <td>${_('none')}</td>
4 <td>${_('read')}</td>
5 <td>${_('write')}</td>
6 <td>${_('admin')}</td>
7 <td>${_('member')}</td>
8 <td></td>
9 </tr>
10 ## USERS
11 %for r2p in c.repos_group.repo_group_to_perm:
12 <tr id="id${id(r2p.user.username)}">
13 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
14 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
17 <td style="white-space: nowrap;">
18 <img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${r2p.user.username}
19 </td>
20 <td>
21 %if r2p.user.username !='default':
22 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
23 ${_('revoke')}
24 </span>
25 %endif
26 </td>
27 </tr>
28 %endfor
29
30 ## USERS GROUPS
31 %for g2p in c.repos_group.users_group_to_perm:
32 <tr id="id${id(g2p.users_group.users_group_name)}">
33 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
34 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
35 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
36 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
37 <td style="white-space: nowrap;">
38 <img style="vertical-align:bottom" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
39 </td>
40 <td>
41 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
42 ${_('revoke')}
43 </span>
44 </td>
45 </tr>
46 %endfor
47 <tr id="add_perm_input">
48 <td>${h.radio('perm_new_member','group.none')}</td>
49 <td>${h.radio('perm_new_member','group.read')}</td>
50 <td>${h.radio('perm_new_member','group.write')}</td>
51 <td>${h.radio('perm_new_member','group.admin')}</td>
52 <td class='ac'>
53 <div class="perm_ac" id="perm_ac">
54 ${h.text('perm_new_member_name',class_='yui-ac-input')}
55 ${h.hidden('perm_new_member_type')}
56 <div id="perm_container"></div>
57 </div>
58 </td>
59 <td></td>
60 </tr>
61 <tr>
62 <td colspan="6">
63 <span id="add_perm" class="add_icon" style="cursor: pointer;">
64 ${_('Add another member')}
65 </span>
66 </td>
67 </tr>
68 </table>
69 <script type="text/javascript">
70 function ajaxActionUser(user_id, field_id) {
71 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.name)}";
72 var callback = {
73 success: function (o) {
74 var tr = YUD.get(String(field_id));
75 tr.parentNode.removeChild(tr);
76 },
77 failure: function (o) {
78 alert("${_('Failed to remove user')}");
79 },
80 };
81 var postData = '_method=delete&user_id=' + user_id;
82 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
83 };
84
85 function ajaxActionUsersGroup(users_group_id,field_id){
86 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.name)}";
87 var callback = {
88 success:function(o){
89 var tr = YUD.get(String(field_id));
90 tr.parentNode.removeChild(tr);
91 },
92 failure:function(o){
93 alert("${_('Failed to remove users group')}");
94 },
95 };
96 var postData = '_method=delete&users_group_id='+users_group_id;
97 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
98 };
99
100 YUE.onDOMReady(function () {
101 if (!YUD.hasClass('perm_new_member_name', 'error')) {
102 YUD.setStyle('add_perm_input', 'display', 'none');
103 }
104 YAHOO.util.Event.addListener('add_perm', 'click', function () {
105 YUD.setStyle('add_perm_input', 'display', '');
106 YUD.setStyle('add_perm', 'opacity', '0.6');
107 YUD.setStyle('add_perm', 'cursor', 'default');
108 });
109 });
110
111 YAHOO.example.FnMultipleFields = function () {
112 var myUsers = ${c.users_array|n};
113 var myGroups = ${c.users_groups_array|n};
114
115 // Define a custom search function for the DataSource of users
116 var matchUsers = function (sQuery) {
117 // Case insensitive matching
118 var query = sQuery.toLowerCase();
119 var i = 0;
120 var l = myUsers.length;
121 var matches = [];
122
123 // Match against each name of each contact
124 for (; i < l; i++) {
125 contact = myUsers[i];
126 if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
127 matches[matches.length] = contact;
128 }
129 }
130 return matches;
131 };
132
133 // Define a custom search function for the DataSource of usersGroups
134 var matchGroups = function (sQuery) {
135 // Case insensitive matching
136 var query = sQuery.toLowerCase();
137 var i = 0;
138 var l = myGroups.length;
139 var matches = [];
140
141 // Match against each name of each contact
142 for (; i < l; i++) {
143 matched_group = myGroups[i];
144 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
145 matches[matches.length] = matched_group;
146 }
147 }
148 return matches;
149 };
150
151 //match all
152 var matchAll = function (sQuery) {
153 u = matchUsers(sQuery);
154 g = matchGroups(sQuery);
155 return u.concat(g);
156 };
157
158 // DataScheme for members
159 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
160 memberDS.responseSchema = {
161 fields: ["id", "fname", "lname", "nname", "grname", "grmembers"]
162 };
163
164 // DataScheme for owner
165 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
166 ownerDS.responseSchema = {
167 fields: ["id", "fname", "lname", "nname"]
168 };
169
170 // Instantiate AutoComplete for perms
171 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
172 membersAC.useShadow = false;
173 membersAC.resultTypeList = false;
174
175 // Instantiate AutoComplete for owner
176 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
177 ownerAC.useShadow = false;
178 ownerAC.resultTypeList = false;
179
180
181 // Helper highlight function for the formatter
182 var highlightMatch = function (full, snippet, matchindex) {
183 return full.substring(0, matchindex) + "<span class='match'>" + full.substr(matchindex, snippet.length) + "</span>" + full.substring(matchindex + snippet.length);
184 };
185
186 // Custom formatter to highlight the matching letters
187 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
188 var query = sQuery.toLowerCase();
189
190 if (oResultData.grname != undefined) {
191 var grname = oResultData.grname;
192 var grmembers = oResultData.grmembers;
193 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
194 var grprefix = "${_('Group')}: ";
195 var grsuffix = " (" + grmembers + " ${_('members')})";
196
197 if (grnameMatchIndex > -1) {
198 return grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix;
199 }
200
201 return grprefix + oResultData.grname + grsuffix;
202 } else if (oResultData.fname != undefined) {
203
204 var fname = oResultData.fname,
205 lname = oResultData.lname,
206 nname = oResultData.nname || "",
207 // Guard against null value
208 fnameMatchIndex = fname.toLowerCase().indexOf(query),
209 lnameMatchIndex = lname.toLowerCase().indexOf(query),
210 nnameMatchIndex = nname.toLowerCase().indexOf(query),
211 displayfname, displaylname, displaynname;
212
213 if (fnameMatchIndex > -1) {
214 displayfname = highlightMatch(fname, query, fnameMatchIndex);
215 } else {
216 displayfname = fname;
217 }
218
219 if (lnameMatchIndex > -1) {
220 displaylname = highlightMatch(lname, query, lnameMatchIndex);
221 } else {
222 displaylname = lname;
223 }
224
225 if (nnameMatchIndex > -1) {
226 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
227 } else {
228 displaynname = nname ? "(" + nname + ")" : "";
229 }
230
231 return displayfname + " " + displaylname + " " + displaynname;
232 } else {
233 return '';
234 }
235 };
236 membersAC.formatResult = custom_formatter;
237 ownerAC.formatResult = custom_formatter;
238
239 var myHandler = function (sType, aArgs) {
240
241 var myAC = aArgs[0]; // reference back to the AC instance
242 var elLI = aArgs[1]; // reference to the selected LI element
243 var oData = aArgs[2]; // object literal of selected item's result data
244 //fill the autocomplete with value
245 if (oData.nname != undefined) {
246 //users
247 myAC.getInputEl().value = oData.nname;
248 YUD.get('perm_new_member_type').value = 'user';
249 } else {
250 //groups
251 myAC.getInputEl().value = oData.grname;
252 YUD.get('perm_new_member_type').value = 'users_group';
253 }
254
255 };
256
257 membersAC.itemSelectEvent.subscribe(myHandler);
258 if(ownerAC.itemSelectEvent){
259 ownerAC.itemSelectEvent.subscribe(myHandler);
260 }
261
262 return {
263 memberDS: memberDS,
264 ownerDS: ownerDS,
265 membersAC: membersAC,
266 ownerAC: ownerAC,
267 };
268 }();
269
270 </script>
@@ -1,149 +1,149 b''
1 ========================
1 ========================
2 RhodeCode documentation!
2 RhodeCode documentation!
3 ========================
3 ========================
4
4
5 ``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_
5 ``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_
6 with a built in push/pull server and full text search.
6 with a built in push/pull server and full text search and code-review.
7 It works on http/https and has a built in permission/authentication system with
7 It works on http/https and has a built in permission/authentication system with
8 the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also supports
8 the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also provides
9 simple API so it's easy integrable with existing external systems.
9 simple API so it's easy integrable with existing external systems.
10
10
11 RhodeCode is similar in some respects to github or bitbucket_,
11 RhodeCode is similar in some respects to github or bitbucket_,
12 however RhodeCode can be run as standalone hosted application on your own server.
12 however RhodeCode can be run as standalone hosted application on your own server.
13 It is open source and donation ware and focuses more on providing a customized,
13 It is open source and donation ware and focuses more on providing a customized,
14 self administered interface for Mercurial and GIT repositories.
14 self administered interface for Mercurial and GIT repositories.
15 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
15 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
16 handle multiple different version control systems.
16 handle multiple different version control systems.
17
17
18 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
18 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
19
19
20 RhodeCode demo
20 RhodeCode demo
21 --------------
21 --------------
22
22
23 http://demo.rhodecode.org
23 http://demo.rhodecode.org
24
24
25 The default access is anonymous but you can login to an administrative account
25 The default access is anonymous but you can login to an administrative account
26 using the following credentials:
26 using the following credentials:
27
27
28 - username: demo
28 - username: demo
29 - password: demo12
29 - password: demo12
30
30
31 Source code
31 Source code
32 -----------
32 -----------
33
33
34 The latest sources can be obtained from official RhodeCode instance
34 The latest sources can be obtained from official RhodeCode instance
35 https://secure.rhodecode.org
35 https://secure.rhodecode.org
36
36
37
37
38 MIRRORS:
38 MIRRORS:
39
39
40 Issue tracker and sources at bitbucket_
40 Issue tracker and sources at bitbucket_
41
41
42 http://bitbucket.org/marcinkuzminski/rhodecode
42 http://bitbucket.org/marcinkuzminski/rhodecode
43
43
44 Sources at github_
44 Sources at github_
45
45
46 https://github.com/marcinkuzminski/rhodecode
46 https://github.com/marcinkuzminski/rhodecode
47
47
48 Installation
48 Installation
49 ------------
49 ------------
50
50
51 Please visit http://packages.python.org/RhodeCode/installation.html
51 Please visit http://packages.python.org/RhodeCode/installation.html
52
52
53
53
54 RhodeCode Features
54 RhodeCode Features
55 ------------------
55 ------------------
56
56
57 - Has its own middleware to handle mercurial_ protocol requests.
57 - Has its own middleware to handle mercurial_ protocol requests.
58 Each request can be logged and authenticated.
58 Each request can be logged and authenticated.
59 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
59 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
60 Supports http/https and LDAP
60 Supports http/https and LDAP
61 - Full permissions (private/read/write/admin) and authentication per project.
61 - Full permissions (private/read/write/admin) and authentication per project.
62 One account for web interface and mercurial_ push/pull/clone operations.
62 One account for web interface and mercurial_ push/pull/clone operations.
63 - Have built in users groups for easier permission management
63 - Have built in users groups for easier permission management
64 - Repository groups let you group repos and manage them easier.
64 - Repository groups let you group repos and manage them easier.
65 - Users can fork other users repo. RhodeCode have also compare view to see
65 - Users can fork other users repo. RhodeCode have also compare view to see
66 combined changeset for all changeset made within single push.
66 combined changeset for all changeset made within single push.
67 - Build in commit-api let's you add, edit and commit files right from RhodeCode
67 - Build in commit-api let's you add, edit and commit files right from RhodeCode
68 interface using simple editor or upload form for binaries.
68 interface using simple editor or upload form for binaries.
69 - Mako templates let's you customize the look and feel of the application.
69 - Mako templates let's you customize the look and feel of the application.
70 - Beautiful diffs, annotations and source code browsing all colored by pygments.
70 - Beautiful diffs, annotations and source code browsing all colored by pygments.
71 Raw diffs are made in git-diff format, including git_ binary-patches
71 Raw diffs are made in git-diff format, including git_ binary-patches
72 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
72 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
73 - Admin interface with user/permission management. Admin activity journal, logs
73 - Admin interface with user/permission management. Admin activity journal, logs
74 pulls, pushes, forks, registrations and other actions made by all users.
74 pulls, pushes, forks, registrations and other actions made by all users.
75 - Server side forks. It is possible to fork a project and modify it freely
75 - Server side forks. It is possible to fork a project and modify it freely
76 without breaking the main repository. You can even write Your own hooks
76 without breaking the main repository. You can even write Your own hooks
77 and install them
77 and install them
78 - code review with notification system, inline commenting, all parsed using
78 - code review with notification system, inline commenting, all parsed using
79 rst syntax
79 rst syntax
80 - rst and markdown README support for repositories
80 - rst and markdown README support for repositories
81 - Full text search powered by Whoosh on the source files, and file names.
81 - Full text search powered by Whoosh on the source files, and file names.
82 Build in indexing daemons, with optional incremental index build
82 Build in indexing daemons, with optional incremental index build
83 (no external search servers required all in one application)
83 (no external search servers required all in one application)
84 - Setup project descriptions and info inside built in db for easy, non
84 - Setup project descriptions and info inside built in db for easy, non
85 file-system operations
85 file-system operations
86 - Intelligent cache with invalidation after push or project change, provides
86 - Intelligent cache with invalidation after push or project change, provides
87 high performance and always up to date data.
87 high performance and always up to date data.
88 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
88 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
89 - Async tasks for speed and performance using celery_ (works without them too)
89 - Async tasks for speed and performance using celery_ (works without them too)
90 - Backup scripts can do backup of whole app and send it over scp to desired
90 - Backup scripts can do backup of whole app and send it over scp to desired
91 location
91 location
92 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
92 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
93
93
94
94
95 .. include:: ./docs/screenshots.rst
95 .. include:: ./docs/screenshots.rst
96
96
97
97
98 Incoming / Plans
98 Incoming / Plans
99 ----------------
99 ----------------
100
100
101 - Finer granular permissions per branch, repo group or subrepo
101 - Finer granular permissions per branch, repo group or subrepo
102 - pull requests and web based merges
102 - pull requests and web based merges
103 - per line file history
103 - per line file history
104 - SSH based authentication with server side key management
104 - SSH based authentication with server side key management
105 - Commit based built in wiki system
105 - Commit based built in wiki system
106 - More statistics and graph (global annotation + some more statistics)
106 - More statistics and graph (global annotation + some more statistics)
107 - Other advancements as development continues (or you can of course make
107 - Other advancements as development continues (or you can of course make
108 additions and or requests)
108 additions and or requests)
109
109
110 License
110 License
111 -------
111 -------
112
112
113 ``RhodeCode`` is released under the GPLv3 license.
113 ``RhodeCode`` is released under the GPLv3 license.
114
114
115
115
116 Mailing group Q&A
116 Mailing group Q&A
117 -----------------
117 -----------------
118
118
119 Join the `Google group <http://groups.google.com/group/rhodecode>`_
119 Join the `Google group <http://groups.google.com/group/rhodecode>`_
120
120
121 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
121 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
122
122
123 Join #rhodecode on FreeNode (irc.freenode.net)
123 Join #rhodecode on FreeNode (irc.freenode.net)
124 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
124 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
125
125
126 Online documentation
126 Online documentation
127 --------------------
127 --------------------
128
128
129 Online documentation for the current version of RhodeCode is available at
129 Online documentation for the current version of RhodeCode is available at
130 http://packages.python.org/RhodeCode/.
130 http://packages.python.org/RhodeCode/.
131 You may also build the documentation for yourself - go into ``docs/`` and run::
131 You may also build the documentation for yourself - go into ``docs/`` and run::
132
132
133 make html
133 make html
134
134
135 (You need to have sphinx_ installed to build the documentation. If you don't
135 (You need to have sphinx_ installed to build the documentation. If you don't
136 have sphinx_ installed you can install it via the command:
136 have sphinx_ installed you can install it via the command:
137 ``easy_install sphinx``)
137 ``easy_install sphinx``)
138
138
139 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
139 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
140 .. _python: http://www.python.org/
140 .. _python: http://www.python.org/
141 .. _sphinx: http://sphinx.pocoo.org/
141 .. _sphinx: http://sphinx.pocoo.org/
142 .. _mercurial: http://mercurial.selenic.com/
142 .. _mercurial: http://mercurial.selenic.com/
143 .. _bitbucket: http://bitbucket.org/
143 .. _bitbucket: http://bitbucket.org/
144 .. _github: http://github.com/
144 .. _github: http://github.com/
145 .. _subversion: http://subversion.tigris.org/
145 .. _subversion: http://subversion.tigris.org/
146 .. _git: http://git-scm.com/
146 .. _git: http://git-scm.com/
147 .. _celery: http://celeryproject.org/
147 .. _celery: http://celeryproject.org/
148 .. _Sphinx: http://sphinx.pocoo.org/
148 .. _Sphinx: http://sphinx.pocoo.org/
149 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
149 .. _vcs: http://pypi.python.org/pypi/vcs
@@ -1,473 +1,541 b''
1 .. _api:
1 .. _api:
2
2
3
3
4 API
4 API
5 ===
5 ===
6
6
7
7
8 Starting from RhodeCode version 1.2 a simple API was implemented.
8 Starting from RhodeCode version 1.2 a simple API was implemented.
9 There's a single schema for calling all api methods. API is implemented
9 There's a single schema for calling all api methods. API is implemented
10 with JSON protocol both ways. An url to send API request in RhodeCode is
10 with JSON protocol both ways. An url to send API request in RhodeCode is
11 <your_server>/_admin/api
11 <your_server>/_admin/api
12
12
13 API ACCESS FOR WEB VIEWS
13 API ACCESS FOR WEB VIEWS
14 ++++++++++++++++++++++++
14 ++++++++++++++++++++++++
15
15
16 API access can also be turned on for each web view in RhodeCode that is
16 API access can also be turned on for each web view in RhodeCode that is
17 decorated with `@LoginRequired` decorator. To enable API access simple change
17 decorated with `@LoginRequired` decorator. To enable API access simple change
18 the standard login decorator to `@LoginRequired(api_access=True)`.
18 the standard login decorator to `@LoginRequired(api_access=True)`.
19 After this change, a rhodecode view can be accessed without login by adding a
19 After this change, a rhodecode view can be accessed without login by adding a
20 GET parameter `?api_key=<api_key>` to url. By default this is only
20 GET parameter `?api_key=<api_key>` to url. By default this is only
21 enabled on RSS/ATOM feed views.
21 enabled on RSS/ATOM feed views.
22
22
23
23
24 API ACCESS
24 API ACCESS
25 ++++++++++
25 ++++++++++
26
26
27 All clients are required to send JSON-RPC spec JSON data::
27 All clients are required to send JSON-RPC spec JSON data::
28
28
29 {
29 {
30 "id:<id>,
30 "id:<id>,
31 "api_key":"<api_key>",
31 "api_key":"<api_key>",
32 "method":"<method_name>",
32 "method":"<method_name>",
33 "args":{"<arg_key>":"<arg_val>"}
33 "args":{"<arg_key>":"<arg_val>"}
34 }
34 }
35
35
36 Example call for autopulling remotes repos using curl::
36 Example call for autopulling remotes repos using curl::
37 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
37 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
38
38
39 Simply provide
39 Simply provide
40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
41 - *api_key* for access and permission validation.
41 - *api_key* for access and permission validation.
42 - *method* is name of method to call
42 - *method* is name of method to call
43 - *args* is an key:value list of arguments to pass to method
43 - *args* is an key:value list of arguments to pass to method
44
44
45 .. note::
45 .. note::
46
46
47 api_key can be found in your user account page
47 api_key can be found in your user account page
48
48
49
49
50 RhodeCode API will return always a JSON-RPC response::
50 RhodeCode API will return always a JSON-RPC response::
51
51
52 {
52 {
53 "id":<id>,
53 "id":<id>,
54 "result": "<result>",
54 "result": "<result>",
55 "error": null
55 "error": null
56 }
56 }
57
57
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
59 calling api *error* key from response will contain failure description
59 calling api *error* key from response will contain failure description
60 and result will be null.
60 and result will be null.
61
61
62 API METHODS
62 API METHODS
63 +++++++++++
63 +++++++++++
64
64
65
65
66 pull
66 pull
67 ----
67 ----
68
68
69 Pulls given repo from remote location. Can be used to automatically keep
69 Pulls given repo from remote location. Can be used to automatically keep
70 remote repos up to date. This command can be executed only using api_key
70 remote repos up to date. This command can be executed only using api_key
71 belonging to user with admin rights
71 belonging to user with admin rights
72
72
73 INPUT::
73 INPUT::
74
74
75 api_key : "<api_key>"
75 api_key : "<api_key>"
76 method : "pull"
76 method : "pull"
77 args : {
77 args : {
78 "repo_name" : "<reponame>"
78 "repo_name" : "<reponame>"
79 }
79 }
80
80
81 OUTPUT::
81 OUTPUT::
82
82
83 result : "Pulled from <reponame>"
83 result : "Pulled from <reponame>"
84 error : null
84 error : null
85
85
86
86
87 get_user
87 get_user
88 --------
88 --------
89
89
90 Get's an user by username, Returns empty result if user is not found.
90 Get's an user by username, Returns empty result if user is not found.
91 This command can be executed only using api_key belonging to user with admin
91 This command can be executed only using api_key belonging to user with admin
92 rights.
92 rights.
93
93
94
94 INPUT::
95 INPUT::
95
96
96 api_key : "<api_key>"
97 api_key : "<api_key>"
97 method : "get_user"
98 method : "get_user"
98 args : {
99 args : {
99 "username" : "<username>"
100 "username" : "<username>"
100 }
101 }
101
102
102 OUTPUT::
103 OUTPUT::
103
104
104 result: None if user does not exist or
105 result: None if user does not exist or
105 {
106 {
106 "id" : "<id>",
107 "id" : "<id>",
107 "username" : "<username>",
108 "username" : "<username>",
108 "firstname": "<firstname>",
109 "firstname": "<firstname>",
109 "lastname" : "<lastname>",
110 "lastname" : "<lastname>",
110 "email" : "<email>",
111 "email" : "<email>",
111 "active" : "<bool>",
112 "active" : "<bool>",
112 "admin" :Β  "<bool>",
113 "admin" :Β  "<bool>",
113 "ldap" : "<ldap_dn>"
114 "ldap" : "<ldap_dn>"
114 }
115 }
115
116
116 error: null
117 error: null
117
118
118
119
119 get_users
120 get_users
120 ---------
121 ---------
121
122
122 Lists all existing users. This command can be executed only using api_key
123 Lists all existing users. This command can be executed only using api_key
123 belonging to user with admin rights.
124 belonging to user with admin rights.
124
125
126
125 INPUT::
127 INPUT::
126
128
127 api_key : "<api_key>"
129 api_key : "<api_key>"
128 method : "get_users"
130 method : "get_users"
129 args : { }
131 args : { }
130
132
131 OUTPUT::
133 OUTPUT::
132
134
133 result: [
135 result: [
134 {
136 {
135 "id" : "<id>",
137 "id" : "<id>",
136 "username" : "<username>",
138 "username" : "<username>",
137 "firstname": "<firstname>",
139 "firstname": "<firstname>",
138 "lastname" : "<lastname>",
140 "lastname" : "<lastname>",
139 "email" : "<email>",
141 "email" : "<email>",
140 "active" : "<bool>",
142 "active" : "<bool>",
141 "admin" :Β  "<bool>",
143 "admin" :Β  "<bool>",
142 "ldap" : "<ldap_dn>"
144 "ldap" : "<ldap_dn>"
143 },
145 },
144 …
146 …
145 ]
147 ]
146 error: null
148 error: null
147
149
150
148 create_user
151 create_user
149 -----------
152 -----------
150
153
151 Creates new user or updates current one if such user exists. This command can
154 Creates new user or updates current one if such user exists. This command can
152 be executed only using api_key belonging to user with admin rights.
155 be executed only using api_key belonging to user with admin rights.
153
156
157
154 INPUT::
158 INPUT::
155
159
156 api_key : "<api_key>"
160 api_key : "<api_key>"
157 method : "create_user"
161 method : "create_user"
158 args : {
162 args : {
159 "username" : "<username>",
163 "username" : "<username>",
160 "password" : "<password>",
164 "password" : "<password>",
161 "email" : "<useremail>",
165 "email" : "<useremail>",
162 "firstname" : "<firstname> = None",
166 "firstname" : "<firstname> = None",
163 "lastname" : "<lastname> = None",
167 "lastname" : "<lastname> = None",
164 "active" : "<bool> = True",
168 "active" : "<bool> = True",
165 "admin" : "<bool> = False",
169 "admin" : "<bool> = False",
166 "ldap_dn" : "<ldap_dn> = None"
170 "ldap_dn" : "<ldap_dn> = None"
167 }
171 }
168
172
169 OUTPUT::
173 OUTPUT::
170
174
171 result: {
175 result: {
172 "id" : "<new_user_id>",
176 "id" : "<new_user_id>",
173 "msg" : "created new user <username>"
177 "msg" : "created new user <username>"
174 }
178 }
175 error: null
179 error: null
176
180
181
177 get_users_group
182 get_users_group
178 ---------------
183 ---------------
179
184
180 Gets an existing users group. This command can be executed only using api_key
185 Gets an existing users group. This command can be executed only using api_key
181 belonging to user with admin rights.
186 belonging to user with admin rights.
182
187
188
183 INPUT::
189 INPUT::
184
190
185 api_key : "<api_key>"
191 api_key : "<api_key>"
186 method : "get_users_group"
192 method : "get_users_group"
187 args : {
193 args : {
188 "group_name" : "<name>"
194 "group_name" : "<name>"
189 }
195 }
190
196
191 OUTPUT::
197 OUTPUT::
192
198
193 result : None if group not exist
199 result : None if group not exist
194 {
200 {
195 "id" : "<id>",
201 "id" : "<id>",
196 "group_name" : "<groupname>",
202 "group_name" : "<groupname>",
197 "active": "<bool>",
203 "active": "<bool>",
198 "members" : [
204 "members" : [
199 { "id" : "<userid>",
205 { "id" : "<userid>",
200 "username" : "<username>",
206 "username" : "<username>",
201 "firstname": "<firstname>",
207 "firstname": "<firstname>",
202 "lastname" : "<lastname>",
208 "lastname" : "<lastname>",
203 "email" : "<email>",
209 "email" : "<email>",
204 "active" : "<bool>",
210 "active" : "<bool>",
205 "admin" :Β  "<bool>",
211 "admin" :Β  "<bool>",
206 "ldap" : "<ldap_dn>"
212 "ldap" : "<ldap_dn>"
207 },
213 },
208 …
214 …
209 ]
215 ]
210 }
216 }
211 error : null
217 error : null
212
218
219
213 get_users_groups
220 get_users_groups
214 ----------------
221 ----------------
215
222
216 Lists all existing users groups. This command can be executed only using
223 Lists all existing users groups. This command can be executed only using
217 api_key belonging to user with admin rights.
224 api_key belonging to user with admin rights.
218
225
226
219 INPUT::
227 INPUT::
220
228
221 api_key : "<api_key>"
229 api_key : "<api_key>"
222 method : "get_users_groups"
230 method : "get_users_groups"
223 args : { }
231 args : { }
224
232
225 OUTPUT::
233 OUTPUT::
226
234
227 result : [
235 result : [
228 {
236 {
229 "id" : "<id>",
237 "id" : "<id>",
230 "group_name" : "<groupname>",
238 "group_name" : "<groupname>",
231 "active": "<bool>",
239 "active": "<bool>",
232 "members" : [
240 "members" : [
233 {
241 {
234 "id" : "<userid>",
242 "id" : "<userid>",
235 "username" : "<username>",
243 "username" : "<username>",
236 "firstname": "<firstname>",
244 "firstname": "<firstname>",
237 "lastname" : "<lastname>",
245 "lastname" : "<lastname>",
238 "email" : "<email>",
246 "email" : "<email>",
239 "active" : "<bool>",
247 "active" : "<bool>",
240 "admin" :Β  "<bool>",
248 "admin" :Β  "<bool>",
241 "ldap" : "<ldap_dn>"
249 "ldap" : "<ldap_dn>"
242 },
250 },
243 …
251 …
244 ]
252 ]
245 }
253 }
246 ]
254 ]
247 error : null
255 error : null
248
256
249
257
250 create_users_group
258 create_users_group
251 ------------------
259 ------------------
252
260
253 Creates new users group. This command can be executed only using api_key
261 Creates new users group. This command can be executed only using api_key
254 belonging to user with admin rights
262 belonging to user with admin rights
255
263
264
256 INPUT::
265 INPUT::
257
266
258 api_key : "<api_key>"
267 api_key : "<api_key>"
259 method : "create_users_group"
268 method : "create_users_group"
260 args: {
269 args: {
261 "group_name": "<groupname>",
270 "group_name": "<groupname>",
262 "active":"<bool> = True"
271 "active":"<bool> = True"
263 }
272 }
264
273
265 OUTPUT::
274 OUTPUT::
266
275
267 result: {
276 result: {
268 "id": "<newusersgroupid>",
277 "id": "<newusersgroupid>",
269 "msg": "created new users group <groupname>"
278 "msg": "created new users group <groupname>"
270 }
279 }
271 error: null
280 error: null
272
281
282
273 add_user_to_users_group
283 add_user_to_users_group
274 -----------------------
284 -----------------------
275
285
276 Adds a user to a users group. This command can be executed only using api_key
286 Adds a user to a users group. This command can be executed only using api_key
277 belonging to user with admin rights
287 belonging to user with admin rights
278
288
289
279 INPUT::
290 INPUT::
280
291
281 api_key : "<api_key>"
292 api_key : "<api_key>"
282 method : "add_user_users_group"
293 method : "add_user_users_group"
283 args: {
294 args: {
284 "group_name" : "<groupname>",
295 "group_name" : "<groupname>",
285 "username" : "<username>"
296 "username" : "<username>"
286 }
297 }
287
298
288 OUTPUT::
299 OUTPUT::
289
300
290 result: {
301 result: {
291 "id": "<newusersgroupmemberid>",
302 "id": "<newusersgroupmemberid>",
292 "msg": "created new users group member"
303 "msg": "created new users group member"
293 }
304 }
294 error: null
305 error: null
295
306
307
296 get_repo
308 get_repo
297 --------
309 --------
298
310
299 Gets an existing repository. This command can be executed only using api_key
311 Gets an existing repository. This command can be executed only using api_key
300 belonging to user with admin rights
312 belonging to user with admin rights
301
313
314
302 INPUT::
315 INPUT::
303
316
304 api_key : "<api_key>"
317 api_key : "<api_key>"
305 method : "get_repo"
318 method : "get_repo"
306 args: {
319 args: {
307 "repo_name" : "<reponame>"
320 "repo_name" : "<reponame>"
308 }
321 }
309
322
310 OUTPUT::
323 OUTPUT::
311
324
312 result: None if repository does not exist or
325 result: None if repository does not exist or
313 {
326 {
314 "id" : "<id>",
327 "id" : "<id>",
315 "repo_name" : "<reponame>"
328 "repo_name" : "<reponame>"
316 "type" : "<type>",
329 "type" : "<type>",
317 "description" : "<description>",
330 "description" : "<description>",
318 "members" : [
331 "members" : [
319 { "id" : "<userid>",
332 { "id" : "<userid>",
320 "username" : "<username>",
333 "username" : "<username>",
321 "firstname": "<firstname>",
334 "firstname": "<firstname>",
322 "lastname" : "<lastname>",
335 "lastname" : "<lastname>",
323 "email" : "<email>",
336 "email" : "<email>",
324 "active" : "<bool>",
337 "active" : "<bool>",
325 "admin" :Β  "<bool>",
338 "admin" :Β  "<bool>",
326 "ldap" : "<ldap_dn>",
339 "ldap" : "<ldap_dn>",
327 "permission" : "repository.(read|write|admin)"
340 "permission" : "repository.(read|write|admin)"
328 },
341 },
329 …
342 …
330 {
343 {
331 "id" : "<usersgroupid>",
344 "id" : "<usersgroupid>",
332 "name" : "<usersgroupname>",
345 "name" : "<usersgroupname>",
333 "active": "<bool>",
346 "active": "<bool>",
334 "permission" : "repository.(read|write|admin)"
347 "permission" : "repository.(read|write|admin)"
335 },
348 },
336 …
349 …
337 ]
350 ]
338 }
351 }
339 error: null
352 error: null
340
353
354
341 get_repos
355 get_repos
342 ---------
356 ---------
343
357
344 Lists all existing repositories. This command can be executed only using api_key
358 Lists all existing repositories. This command can be executed only using api_key
345 belonging to user with admin rights
359 belonging to user with admin rights
346
360
361
347 INPUT::
362 INPUT::
348
363
349 api_key : "<api_key>"
364 api_key : "<api_key>"
350 method : "get_repos"
365 method : "get_repos"
351 args: { }
366 args: { }
352
367
353 OUTPUT::
368 OUTPUT::
354
369
355 result: [
370 result: [
356 {
371 {
357 "id" : "<id>",
372 "id" : "<id>",
358 "repo_name" : "<reponame>"
373 "repo_name" : "<reponame>"
359 "type" : "<type>",
374 "type" : "<type>",
360 "description" : "<description>"
375 "description" : "<description>"
361 },
376 },
362 …
377 …
363 ]
378 ]
364 error: null
379 error: null
365
380
366
381
367 get_repo_nodes
382 get_repo_nodes
368 --------------
383 --------------
369
384
370 returns a list of nodes and it's children in a flat list for a given path
385 returns a list of nodes and it's children in a flat list for a given path
371 at given revision. It's possible to specify ret_type to show only `files` or
386 at given revision. It's possible to specify ret_type to show only `files` or
372 `dirs`. This command can be executed only using api_key belonging to user
387 `dirs`. This command can be executed only using api_key belonging to user
373 with admin rights
388 with admin rights
374
389
390
375 INPUT::
391 INPUT::
376
392
377 api_key : "<api_key>"
393 api_key : "<api_key>"
378 method : "get_repo_nodes"
394 method : "get_repo_nodes"
379 args: {
395 args: {
380 "repo_name" : "<reponame>",
396 "repo_name" : "<reponame>",
381 "revision" : "<revision>",
397 "revision" : "<revision>",
382 "root_path" : "<root_path>",
398 "root_path" : "<root_path>",
383 "ret_type" : "<ret_type>" = 'all'
399 "ret_type" : "<ret_type>" = 'all'
384 }
400 }
385
401
386 OUTPUT::
402 OUTPUT::
387
403
388 result: [
404 result: [
389 {
405 {
390 "name" : "<name>"
406 "name" : "<name>"
391 "type" : "<type>",
407 "type" : "<type>",
392 },
408 },
393 …
409 …
394 ]
410 ]
395 error: null
411 error: null
396
412
397
413
398
399 create_repo
414 create_repo
400 -----------
415 -----------
401
416
402 Creates a repository. This command can be executed only using api_key
417 Creates a repository. This command can be executed only using api_key
403 belonging to user with admin rights.
418 belonging to user with admin rights.
404 If repository name contains "/", all needed repository groups will be created.
419 If repository name contains "/", all needed repository groups will be created.
405 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
420 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
406 and create "baz" repository with "bar" as group.
421 and create "baz" repository with "bar" as group.
407
422
423
408 INPUT::
424 INPUT::
409
425
410 api_key : "<api_key>"
426 api_key : "<api_key>"
411 method : "create_repo"
427 method : "create_repo"
412 args: {
428 args: {
413 "repo_name" : "<reponame>",
429 "repo_name" : "<reponame>",
414 "owner_name" : "<ownername>",
430 "owner_name" : "<ownername>",
415 "description" : "<description> = ''",
431 "description" : "<description> = ''",
416 "repo_type" : "<type> = 'hg'",
432 "repo_type" : "<type> = 'hg'",
417 "private" : "<bool> = False"
433 "private" : "<bool> = False"
418 }
434 }
419
435
420 OUTPUT::
436 OUTPUT::
421
437
422 result: {
438 result: {
423 "id": "<newrepoid>",
439 "id": "<newrepoid>",
424 "msg": "Created new repository <reponame>",
440 "msg": "Created new repository <reponame>",
425 }
441 }
426 error: null
442 error: null
427
443
428 add_user_to_repo
444
429 ----------------
445 grant_user_permission
446 ---------------------
430
447
431 Add a user to a repository. This command can be executed only using api_key
448 Grant permission for user on given repository, or update existing one
432 belonging to user with admin rights.
449 if found. This command can be executed only using api_key belonging to user
433 If "perm" is None, user will be removed from the repository.
450 with admin rights.
451
434
452
435 INPUT::
453 INPUT::
436
454
437 api_key : "<api_key>"
455 api_key : "<api_key>"
438 method : "add_user_to_repo"
456 method : "grant_user_permission"
439 args: {
457 args: {
440 "repo_name" : "<reponame>",
458 "repo_name" : "<reponame>",
441 "username" : "<username>",
459 "username" : "<username>",
442 "perm" : "(None|repository.(read|write|admin))",
460 "perm" : "(repository.(none|read|write|admin))",
461 }
462
463 OUTPUT::
464
465 result: {
466 "msg" : "Granted perm: <perm> for user: <username> in repo: <reponame>"
467 }
468 error: null
469
470
471 revoke_user_permission
472 ----------------------
473
474 Revoke permission for user on given repository. This command can be executed
475 only using api_key belonging to user with admin rights.
476
477
478 INPUT::
479
480 api_key : "<api_key>"
481 method : "revoke_user_permission"
482 args: {
483 "repo_name" : "<reponame>",
484 "username" : "<username>",
443 }
485 }
444
486
445 OUTPUT::
487 OUTPUT::
446
488
447 result: {
489 result: {
448 "msg" : "Added perm: <perm> for <username> in repo: <reponame>"
490 "msg" : "Revoked perm for user: <suername> in repo: <reponame>"
449 }
491 }
450 error: null
492 error: null
451
493
452 add_users_group_to_repo
494
453 -----------------------
495 grant_users_group_permission
496 ----------------------------
454
497
455 Add a users group to a repository. This command can be executed only using
498 Grant permission for users group on given repository, or update
456 api_key belonging to user with admin rights. If "perm" is None, group will
499 existing one if found. This command can be executed only using
457 be removed from the repository.
500 api_key belonging to user with admin rights.
501
458
502
459 INPUT::
503 INPUT::
460
504
461 api_key : "<api_key>"
505 api_key : "<api_key>"
462 method : "add_users_group_to_repo"
506 method : "grant_users_group_permission"
507 args: {
508 "repo_name" : "<reponame>",
509 "group_name" : "<usersgroupname>",
510 "perm" : "(repository.(none|read|write|admin))",
511 }
512
513 OUTPUT::
514
515 result: {
516 "msg" : "Granted perm: <perm> for group: <usersgroupname> in repo: <reponame>"
517 }
518 error: null
519
520
521 revoke_users_group_permission
522 -----------------------------
523
524 Revoke permission for users group on given repository.This command can be
525 executed only using api_key belonging to user with admin rights.
526
527 INPUT::
528
529 api_key : "<api_key>"
530 method : "revoke_users_group_permission"
463 args: {
531 args: {
464 "repo_name" : "<reponame>",
532 "repo_name" : "<reponame>",
465 "group_name" : "<groupname>",
533 "users_group" : "<usersgroupname>",
466 "perm" : "(None|repository.(read|write|admin))",
467 }
534 }
535
468 OUTPUT::
536 OUTPUT::
469
537
470 result: {
538 result: {
471 "msg" : Added perm: <perm> for <groupname> in repo: <reponame>"
539 "msg" : "Revoked perm for group: <usersgroupname> in repo: <reponame>"
472 }
540 }
473
541 error: null No newline at end of file
@@ -1,86 +1,85 b''
1 """Pylons middleware initialization"""
1 """Pylons middleware initialization"""
2
2
3 from beaker.middleware import SessionMiddleware
3 from beaker.middleware import SessionMiddleware
4 from routes.middleware import RoutesMiddleware
4 from routes.middleware import RoutesMiddleware
5 from paste.cascade import Cascade
5 from paste.cascade import Cascade
6 from paste.registry import RegistryManager
6 from paste.registry import RegistryManager
7 from paste.urlparser import StaticURLParser
7 from paste.urlparser import StaticURLParser
8 from paste.deploy.converters import asbool
8 from paste.deploy.converters import asbool
9 from paste.gzipper import make_gzip_middleware
9 from paste.gzipper import make_gzip_middleware
10
10
11 from pylons.middleware import ErrorHandler, StatusCodeRedirect
11 from pylons.middleware import ErrorHandler, StatusCodeRedirect
12 from pylons.wsgiapp import PylonsApp
12 from pylons.wsgiapp import PylonsApp
13
13
14 from rhodecode.lib.middleware.simplehg import SimpleHg
14 from rhodecode.lib.middleware.simplehg import SimpleHg
15 from rhodecode.lib.middleware.simplegit import SimpleGit
15 from rhodecode.lib.middleware.simplegit import SimpleGit
16 from rhodecode.lib.middleware.https_fixup import HttpsFixup
16 from rhodecode.lib.middleware.https_fixup import HttpsFixup
17 from rhodecode.config.environment import load_environment
17 from rhodecode.config.environment import load_environment
18
18
19
19
20 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
20 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
21 """Create a Pylons WSGI application and return it
21 """Create a Pylons WSGI application and return it
22
22
23 ``global_conf``
23 ``global_conf``
24 The inherited configuration for this application. Normally from
24 The inherited configuration for this application. Normally from
25 the [DEFAULT] section of the Paste ini file.
25 the [DEFAULT] section of the Paste ini file.
26
26
27 ``full_stack``
27 ``full_stack``
28 Whether or not this application provides a full WSGI stack (by
28 Whether or not this application provides a full WSGI stack (by
29 default, meaning it handles its own exceptions and errors).
29 default, meaning it handles its own exceptions and errors).
30 Disable full_stack when this application is "managed" by
30 Disable full_stack when this application is "managed" by
31 another WSGI middleware.
31 another WSGI middleware.
32
32
33 ``app_conf``
33 ``app_conf``
34 The application's local configuration. Normally specified in
34 The application's local configuration. Normally specified in
35 the [app:<name>] section of the Paste ini file (where <name>
35 the [app:<name>] section of the Paste ini file (where <name>
36 defaults to main).
36 defaults to main).
37
37
38 """
38 """
39 # Configure the Pylons environment
39 # Configure the Pylons environment
40 config = load_environment(global_conf, app_conf)
40 config = load_environment(global_conf, app_conf)
41
41
42 # The Pylons WSGI app
42 # The Pylons WSGI app
43 app = PylonsApp(config=config)
43 app = PylonsApp(config=config)
44
44
45 # Routing/Session/Cache Middleware
45 # Routing/Session/Cache Middleware
46 app = RoutesMiddleware(app, config['routes.map'])
46 app = RoutesMiddleware(app, config['routes.map'])
47 app = SessionMiddleware(app, config)
47 app = SessionMiddleware(app, config)
48
48
49 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
49 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
50 if asbool(config['pdebug']):
50 if asbool(config['pdebug']):
51 from rhodecode.lib.profiler import ProfilingMiddleware
51 from rhodecode.lib.profiler import ProfilingMiddleware
52 app = ProfilingMiddleware(app)
52 app = ProfilingMiddleware(app)
53
53
54 if asbool(full_stack):
54
55
55 if asbool(full_stack):
56 # Handle Python exceptions
56 # Handle Python exceptions
57 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
57 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
58
58
59 # we want our low level middleware to get to the request ASAP. We don't
59 # we want our low level middleware to get to the request ASAP. We don't
60 # need any pylons stack middleware in them
60 # need any pylons stack middleware in them
61 app = SimpleHg(app, config)
61 app = SimpleHg(app, config)
62 app = SimpleGit(app, config)
62 app = SimpleGit(app, config)
63
63
64 # Display error documents for 401, 403, 404 status codes (and
64 # Display error documents for 401, 403, 404 status codes (and
65 # 500 when debug is disabled)
65 # 500 when debug is disabled)
66 if asbool(config['debug']):
66 if asbool(config['debug']):
67 app = StatusCodeRedirect(app)
67 app = StatusCodeRedirect(app)
68 else:
68 else:
69 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
69 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
70
70
71 #enable https redirets based on HTTP_X_URL_SCHEME set by proxy
71 #enable https redirets based on HTTP_X_URL_SCHEME set by proxy
72 app = HttpsFixup(app, config)
72 app = HttpsFixup(app, config)
73
73
74 # Establish the Registry for this application
74 # Establish the Registry for this application
75 app = RegistryManager(app)
75 app = RegistryManager(app)
76
76
77 if asbool(static_files):
77 if asbool(static_files):
78 # Serve static files
78 # Serve static files
79 static_app = StaticURLParser(config['pylons.paths']['static_files'])
79 static_app = StaticURLParser(config['pylons.paths']['static_files'])
80 app = Cascade([static_app, app])
80 app = Cascade([static_app, app])
81 app = make_gzip_middleware(app, global_conf, compress_level=1)
81 app = make_gzip_middleware(app, global_conf, compress_level=1)
82
82
83
84 app.config = config
83 app.config = config
85
84
86 return app
85 return app
@@ -1,497 +1,507 b''
1 """
1 """
2 Routes configuration
2 Routes configuration
3
3
4 The more specific and detailed routes should be defined first so they
4 The more specific and detailed routes should be defined first so they
5 may take precedent over the more generic routes. For more information
5 may take precedent over the more generic routes. For more information
6 refer to the routes manual at http://routes.groovie.org/docs/
6 refer to the routes manual at http://routes.groovie.org/docs/
7 """
7 """
8 from __future__ import with_statement
8 from __future__ import with_statement
9 from routes import Mapper
9 from routes import Mapper
10
10
11 # prefix for non repository related links needs to be prefixed with `/`
11 # prefix for non repository related links needs to be prefixed with `/`
12 ADMIN_PREFIX = '/_admin'
12 ADMIN_PREFIX = '/_admin'
13
13
14
14
15 def make_map(config):
15 def make_map(config):
16 """Create, configure and return the routes Mapper"""
16 """Create, configure and return the routes Mapper"""
17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 always_scan=config['debug'])
18 always_scan=config['debug'])
19 rmap.minimization = False
19 rmap.minimization = False
20 rmap.explicit = False
20 rmap.explicit = False
21
21
22 from rhodecode.lib.utils import is_valid_repo
22 from rhodecode.lib.utils import is_valid_repo
23 from rhodecode.lib.utils import is_valid_repos_group
23 from rhodecode.lib.utils import is_valid_repos_group
24
24
25 def check_repo(environ, match_dict):
25 def check_repo(environ, match_dict):
26 """
26 """
27 check for valid repository for proper 404 handling
27 check for valid repository for proper 404 handling
28
28
29 :param environ:
29 :param environ:
30 :param match_dict:
30 :param match_dict:
31 """
31 """
32 from rhodecode.model.db import Repository
32 from rhodecode.model.db import Repository
33 repo_name = match_dict.get('repo_name')
33 repo_name = match_dict.get('repo_name')
34
34
35 try:
35 try:
36 by_id = repo_name.split('_')
36 by_id = repo_name.split('_')
37 if len(by_id) == 2 and by_id[1].isdigit():
37 if len(by_id) == 2 and by_id[1].isdigit():
38 repo_name = Repository.get(by_id[1]).repo_name
38 repo_name = Repository.get(by_id[1]).repo_name
39 match_dict['repo_name'] = repo_name
39 match_dict['repo_name'] = repo_name
40 except:
40 except:
41 pass
41 pass
42
42
43 return is_valid_repo(repo_name, config['base_path'])
43 return is_valid_repo(repo_name, config['base_path'])
44
44
45 def check_group(environ, match_dict):
45 def check_group(environ, match_dict):
46 """
46 """
47 check for valid repositories group for proper 404 handling
47 check for valid repositories group for proper 404 handling
48
48
49 :param environ:
49 :param environ:
50 :param match_dict:
50 :param match_dict:
51 """
51 """
52 repos_group_name = match_dict.get('group_name')
52 repos_group_name = match_dict.get('group_name')
53
53
54 return is_valid_repos_group(repos_group_name, config['base_path'])
54 return is_valid_repos_group(repos_group_name, config['base_path'])
55
55
56 def check_int(environ, match_dict):
56 def check_int(environ, match_dict):
57 return match_dict.get('id').isdigit()
57 return match_dict.get('id').isdigit()
58
58
59 # The ErrorController route (handles 404/500 error pages); it should
59 # The ErrorController route (handles 404/500 error pages); it should
60 # likely stay at the top, ensuring it can always be resolved
60 # likely stay at the top, ensuring it can always be resolved
61 rmap.connect('/error/{action}', controller='error')
61 rmap.connect('/error/{action}', controller='error')
62 rmap.connect('/error/{action}/{id}', controller='error')
62 rmap.connect('/error/{action}/{id}', controller='error')
63
63
64 #==========================================================================
64 #==========================================================================
65 # CUSTOM ROUTES HERE
65 # CUSTOM ROUTES HERE
66 #==========================================================================
66 #==========================================================================
67
67
68 #MAIN PAGE
68 #MAIN PAGE
69 rmap.connect('home', '/', controller='home', action='index')
69 rmap.connect('home', '/', controller='home', action='index')
70 rmap.connect('repo_switcher', '/repos', controller='home',
70 rmap.connect('repo_switcher', '/repos', controller='home',
71 action='repo_switcher')
71 action='repo_switcher')
72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}',
72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}',
73 controller='home', action='branch_tag_switcher')
73 controller='home', action='branch_tag_switcher')
74 rmap.connect('bugtracker',
74 rmap.connect('bugtracker',
75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
76 _static=True)
76 _static=True)
77 rmap.connect('rst_help',
77 rmap.connect('rst_help',
78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
79 _static=True)
79 _static=True)
80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
81
81
82 #ADMIN REPOSITORY REST ROUTES
82 #ADMIN REPOSITORY REST ROUTES
83 with rmap.submapper(path_prefix=ADMIN_PREFIX,
83 with rmap.submapper(path_prefix=ADMIN_PREFIX,
84 controller='admin/repos') as m:
84 controller='admin/repos') as m:
85 m.connect("repos", "/repos",
85 m.connect("repos", "/repos",
86 action="create", conditions=dict(method=["POST"]))
86 action="create", conditions=dict(method=["POST"]))
87 m.connect("repos", "/repos",
87 m.connect("repos", "/repos",
88 action="index", conditions=dict(method=["GET"]))
88 action="index", conditions=dict(method=["GET"]))
89 m.connect("formatted_repos", "/repos.{format}",
89 m.connect("formatted_repos", "/repos.{format}",
90 action="index",
90 action="index",
91 conditions=dict(method=["GET"]))
91 conditions=dict(method=["GET"]))
92 m.connect("new_repo", "/repos/new",
92 m.connect("new_repo", "/repos/new",
93 action="new", conditions=dict(method=["GET"]))
93 action="new", conditions=dict(method=["GET"]))
94 m.connect("formatted_new_repo", "/repos/new.{format}",
94 m.connect("formatted_new_repo", "/repos/new.{format}",
95 action="new", conditions=dict(method=["GET"]))
95 action="new", conditions=dict(method=["GET"]))
96 m.connect("/repos/{repo_name:.*}",
96 m.connect("/repos/{repo_name:.*}",
97 action="update", conditions=dict(method=["PUT"],
97 action="update", conditions=dict(method=["PUT"],
98 function=check_repo))
98 function=check_repo))
99 m.connect("/repos/{repo_name:.*}",
99 m.connect("/repos/{repo_name:.*}",
100 action="delete", conditions=dict(method=["DELETE"],
100 action="delete", conditions=dict(method=["DELETE"],
101 function=check_repo))
101 function=check_repo))
102 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
102 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
103 action="edit", conditions=dict(method=["GET"],
103 action="edit", conditions=dict(method=["GET"],
104 function=check_repo))
104 function=check_repo))
105 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
105 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
106 action="edit", conditions=dict(method=["GET"],
106 action="edit", conditions=dict(method=["GET"],
107 function=check_repo))
107 function=check_repo))
108 m.connect("repo", "/repos/{repo_name:.*}",
108 m.connect("repo", "/repos/{repo_name:.*}",
109 action="show", conditions=dict(method=["GET"],
109 action="show", conditions=dict(method=["GET"],
110 function=check_repo))
110 function=check_repo))
111 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
111 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
112 action="show", conditions=dict(method=["GET"],
112 action="show", conditions=dict(method=["GET"],
113 function=check_repo))
113 function=check_repo))
114 #ajax delete repo perm user
114 #ajax delete repo perm user
115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
116 action="delete_perm_user", conditions=dict(method=["DELETE"],
116 action="delete_perm_user",
117 function=check_repo))
117 conditions=dict(method=["DELETE"], function=check_repo))
118
118 #ajax delete repo perm users_group
119 #ajax delete repo perm users_group
119 m.connect('delete_repo_users_group',
120 m.connect('delete_repo_users_group',
120 "/repos_delete_users_group/{repo_name:.*}",
121 "/repos_delete_users_group/{repo_name:.*}",
121 action="delete_perm_users_group",
122 action="delete_perm_users_group",
122 conditions=dict(method=["DELETE"], function=check_repo))
123 conditions=dict(method=["DELETE"], function=check_repo))
123
124
124 #settings actions
125 #settings actions
125 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
126 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
126 action="repo_stats", conditions=dict(method=["DELETE"],
127 action="repo_stats", conditions=dict(method=["DELETE"],
127 function=check_repo))
128 function=check_repo))
128 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
129 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
129 action="repo_cache", conditions=dict(method=["DELETE"],
130 action="repo_cache", conditions=dict(method=["DELETE"],
130 function=check_repo))
131 function=check_repo))
131 m.connect('repo_public_journal',"/repos_public_journal/{repo_name:.*}",
132 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*}",
132 action="repo_public_journal", conditions=dict(method=["PUT"],
133 action="repo_public_journal", conditions=dict(method=["PUT"],
133 function=check_repo))
134 function=check_repo))
134 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
135 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
135 action="repo_pull", conditions=dict(method=["PUT"],
136 action="repo_pull", conditions=dict(method=["PUT"],
136 function=check_repo))
137 function=check_repo))
137 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*}",
138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*}",
138 action="repo_as_fork", conditions=dict(method=["PUT"],
139 action="repo_as_fork", conditions=dict(method=["PUT"],
139 function=check_repo))
140 function=check_repo))
140
141
141 with rmap.submapper(path_prefix=ADMIN_PREFIX,
142 with rmap.submapper(path_prefix=ADMIN_PREFIX,
142 controller='admin/repos_groups') as m:
143 controller='admin/repos_groups') as m:
143 m.connect("repos_groups", "/repos_groups",
144 m.connect("repos_groups", "/repos_groups",
144 action="create", conditions=dict(method=["POST"]))
145 action="create", conditions=dict(method=["POST"]))
145 m.connect("repos_groups", "/repos_groups",
146 m.connect("repos_groups", "/repos_groups",
146 action="index", conditions=dict(method=["GET"]))
147 action="index", conditions=dict(method=["GET"]))
147 m.connect("formatted_repos_groups", "/repos_groups.{format}",
148 m.connect("formatted_repos_groups", "/repos_groups.{format}",
148 action="index", conditions=dict(method=["GET"]))
149 action="index", conditions=dict(method=["GET"]))
149 m.connect("new_repos_group", "/repos_groups/new",
150 m.connect("new_repos_group", "/repos_groups/new",
150 action="new", conditions=dict(method=["GET"]))
151 action="new", conditions=dict(method=["GET"]))
151 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
152 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
152 action="new", conditions=dict(method=["GET"]))
153 action="new", conditions=dict(method=["GET"]))
153 m.connect("update_repos_group", "/repos_groups/{id}",
154 m.connect("update_repos_group", "/repos_groups/{id}",
154 action="update", conditions=dict(method=["PUT"],
155 action="update", conditions=dict(method=["PUT"],
155 function=check_int))
156 function=check_int))
156 m.connect("delete_repos_group", "/repos_groups/{id}",
157 m.connect("delete_repos_group", "/repos_groups/{id}",
157 action="delete", conditions=dict(method=["DELETE"],
158 action="delete", conditions=dict(method=["DELETE"],
158 function=check_int))
159 function=check_int))
159 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
160 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
160 action="edit", conditions=dict(method=["GET"],
161 action="edit", conditions=dict(method=["GET"],
161 function=check_int))
162 function=check_int))
162 m.connect("formatted_edit_repos_group",
163 m.connect("formatted_edit_repos_group",
163 "/repos_groups/{id}.{format}/edit",
164 "/repos_groups/{id}.{format}/edit",
164 action="edit", conditions=dict(method=["GET"],
165 action="edit", conditions=dict(method=["GET"],
165 function=check_int))
166 function=check_int))
166 m.connect("repos_group", "/repos_groups/{id}",
167 m.connect("repos_group", "/repos_groups/{id}",
167 action="show", conditions=dict(method=["GET"],
168 action="show", conditions=dict(method=["GET"],
168 function=check_int))
169 function=check_int))
169 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
170 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
170 action="show", conditions=dict(method=["GET"],
171 action="show", conditions=dict(method=["GET"],
171 function=check_int))
172 function=check_int))
173 # ajax delete repos group perm user
174 m.connect('delete_repos_group_user_perm',
175 "/delete_repos_group_user_perm/{group_name:.*}",
176 action="delete_repos_group_user_perm",
177 conditions=dict(method=["DELETE"], function=check_group))
178
179 # ajax delete repos group perm users_group
180 m.connect('delete_repos_group_users_group_perm',
181 "/delete_repos_group_users_group_perm/{group_name:.*}",
182 action="delete_repos_group_users_group_perm",
183 conditions=dict(method=["DELETE"], function=check_group))
172
184
173 #ADMIN USER REST ROUTES
185 #ADMIN USER REST ROUTES
174 with rmap.submapper(path_prefix=ADMIN_PREFIX,
186 with rmap.submapper(path_prefix=ADMIN_PREFIX,
175 controller='admin/users') as m:
187 controller='admin/users') as m:
176 m.connect("users", "/users",
188 m.connect("users", "/users",
177 action="create", conditions=dict(method=["POST"]))
189 action="create", conditions=dict(method=["POST"]))
178 m.connect("users", "/users",
190 m.connect("users", "/users",
179 action="index", conditions=dict(method=["GET"]))
191 action="index", conditions=dict(method=["GET"]))
180 m.connect("formatted_users", "/users.{format}",
192 m.connect("formatted_users", "/users.{format}",
181 action="index", conditions=dict(method=["GET"]))
193 action="index", conditions=dict(method=["GET"]))
182 m.connect("new_user", "/users/new",
194 m.connect("new_user", "/users/new",
183 action="new", conditions=dict(method=["GET"]))
195 action="new", conditions=dict(method=["GET"]))
184 m.connect("formatted_new_user", "/users/new.{format}",
196 m.connect("formatted_new_user", "/users/new.{format}",
185 action="new", conditions=dict(method=["GET"]))
197 action="new", conditions=dict(method=["GET"]))
186 m.connect("update_user", "/users/{id}",
198 m.connect("update_user", "/users/{id}",
187 action="update", conditions=dict(method=["PUT"]))
199 action="update", conditions=dict(method=["PUT"]))
188 m.connect("delete_user", "/users/{id}",
200 m.connect("delete_user", "/users/{id}",
189 action="delete", conditions=dict(method=["DELETE"]))
201 action="delete", conditions=dict(method=["DELETE"]))
190 m.connect("edit_user", "/users/{id}/edit",
202 m.connect("edit_user", "/users/{id}/edit",
191 action="edit", conditions=dict(method=["GET"]))
203 action="edit", conditions=dict(method=["GET"]))
192 m.connect("formatted_edit_user",
204 m.connect("formatted_edit_user",
193 "/users/{id}.{format}/edit",
205 "/users/{id}.{format}/edit",
194 action="edit", conditions=dict(method=["GET"]))
206 action="edit", conditions=dict(method=["GET"]))
195 m.connect("user", "/users/{id}",
207 m.connect("user", "/users/{id}",
196 action="show", conditions=dict(method=["GET"]))
208 action="show", conditions=dict(method=["GET"]))
197 m.connect("formatted_user", "/users/{id}.{format}",
209 m.connect("formatted_user", "/users/{id}.{format}",
198 action="show", conditions=dict(method=["GET"]))
210 action="show", conditions=dict(method=["GET"]))
199
211
200 #EXTRAS USER ROUTES
212 #EXTRAS USER ROUTES
201 m.connect("user_perm", "/users_perm/{id}",
213 m.connect("user_perm", "/users_perm/{id}",
202 action="update_perm", conditions=dict(method=["PUT"]))
214 action="update_perm", conditions=dict(method=["PUT"]))
203
215
204 #ADMIN USERS REST ROUTES
216 #ADMIN USERS REST ROUTES
205 with rmap.submapper(path_prefix=ADMIN_PREFIX,
217 with rmap.submapper(path_prefix=ADMIN_PREFIX,
206 controller='admin/users_groups') as m:
218 controller='admin/users_groups') as m:
207 m.connect("users_groups", "/users_groups",
219 m.connect("users_groups", "/users_groups",
208 action="create", conditions=dict(method=["POST"]))
220 action="create", conditions=dict(method=["POST"]))
209 m.connect("users_groups", "/users_groups",
221 m.connect("users_groups", "/users_groups",
210 action="index", conditions=dict(method=["GET"]))
222 action="index", conditions=dict(method=["GET"]))
211 m.connect("formatted_users_groups", "/users_groups.{format}",
223 m.connect("formatted_users_groups", "/users_groups.{format}",
212 action="index", conditions=dict(method=["GET"]))
224 action="index", conditions=dict(method=["GET"]))
213 m.connect("new_users_group", "/users_groups/new",
225 m.connect("new_users_group", "/users_groups/new",
214 action="new", conditions=dict(method=["GET"]))
226 action="new", conditions=dict(method=["GET"]))
215 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
227 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
216 action="new", conditions=dict(method=["GET"]))
228 action="new", conditions=dict(method=["GET"]))
217 m.connect("update_users_group", "/users_groups/{id}",
229 m.connect("update_users_group", "/users_groups/{id}",
218 action="update", conditions=dict(method=["PUT"]))
230 action="update", conditions=dict(method=["PUT"]))
219 m.connect("delete_users_group", "/users_groups/{id}",
231 m.connect("delete_users_group", "/users_groups/{id}",
220 action="delete", conditions=dict(method=["DELETE"]))
232 action="delete", conditions=dict(method=["DELETE"]))
221 m.connect("edit_users_group", "/users_groups/{id}/edit",
233 m.connect("edit_users_group", "/users_groups/{id}/edit",
222 action="edit", conditions=dict(method=["GET"]))
234 action="edit", conditions=dict(method=["GET"]))
223 m.connect("formatted_edit_users_group",
235 m.connect("formatted_edit_users_group",
224 "/users_groups/{id}.{format}/edit",
236 "/users_groups/{id}.{format}/edit",
225 action="edit", conditions=dict(method=["GET"]))
237 action="edit", conditions=dict(method=["GET"]))
226 m.connect("users_group", "/users_groups/{id}",
238 m.connect("users_group", "/users_groups/{id}",
227 action="show", conditions=dict(method=["GET"]))
239 action="show", conditions=dict(method=["GET"]))
228 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
240 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
229 action="show", conditions=dict(method=["GET"]))
241 action="show", conditions=dict(method=["GET"]))
230
242
231 #EXTRAS USER ROUTES
243 #EXTRAS USER ROUTES
232 m.connect("users_group_perm", "/users_groups_perm/{id}",
244 m.connect("users_group_perm", "/users_groups_perm/{id}",
233 action="update_perm", conditions=dict(method=["PUT"]))
245 action="update_perm", conditions=dict(method=["PUT"]))
234
246
235 #ADMIN GROUP REST ROUTES
247 #ADMIN GROUP REST ROUTES
236 rmap.resource('group', 'groups',
248 rmap.resource('group', 'groups',
237 controller='admin/groups', path_prefix=ADMIN_PREFIX)
249 controller='admin/groups', path_prefix=ADMIN_PREFIX)
238
250
239 #ADMIN PERMISSIONS REST ROUTES
251 #ADMIN PERMISSIONS REST ROUTES
240 rmap.resource('permission', 'permissions',
252 rmap.resource('permission', 'permissions',
241 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
253 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
242
254
243 ##ADMIN LDAP SETTINGS
255 ##ADMIN LDAP SETTINGS
244 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
256 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
245 controller='admin/ldap_settings', action='ldap_settings',
257 controller='admin/ldap_settings', action='ldap_settings',
246 conditions=dict(method=["POST"]))
258 conditions=dict(method=["POST"]))
247
259
248 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
260 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
249 controller='admin/ldap_settings')
261 controller='admin/ldap_settings')
250
262
251 #ADMIN SETTINGS REST ROUTES
263 #ADMIN SETTINGS REST ROUTES
252 with rmap.submapper(path_prefix=ADMIN_PREFIX,
264 with rmap.submapper(path_prefix=ADMIN_PREFIX,
253 controller='admin/settings') as m:
265 controller='admin/settings') as m:
254 m.connect("admin_settings", "/settings",
266 m.connect("admin_settings", "/settings",
255 action="create", conditions=dict(method=["POST"]))
267 action="create", conditions=dict(method=["POST"]))
256 m.connect("admin_settings", "/settings",
268 m.connect("admin_settings", "/settings",
257 action="index", conditions=dict(method=["GET"]))
269 action="index", conditions=dict(method=["GET"]))
258 m.connect("formatted_admin_settings", "/settings.{format}",
270 m.connect("formatted_admin_settings", "/settings.{format}",
259 action="index", conditions=dict(method=["GET"]))
271 action="index", conditions=dict(method=["GET"]))
260 m.connect("admin_new_setting", "/settings/new",
272 m.connect("admin_new_setting", "/settings/new",
261 action="new", conditions=dict(method=["GET"]))
273 action="new", conditions=dict(method=["GET"]))
262 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
274 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
263 action="new", conditions=dict(method=["GET"]))
275 action="new", conditions=dict(method=["GET"]))
264 m.connect("/settings/{setting_id}",
276 m.connect("/settings/{setting_id}",
265 action="update", conditions=dict(method=["PUT"]))
277 action="update", conditions=dict(method=["PUT"]))
266 m.connect("/settings/{setting_id}",
278 m.connect("/settings/{setting_id}",
267 action="delete", conditions=dict(method=["DELETE"]))
279 action="delete", conditions=dict(method=["DELETE"]))
268 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
280 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
269 action="edit", conditions=dict(method=["GET"]))
281 action="edit", conditions=dict(method=["GET"]))
270 m.connect("formatted_admin_edit_setting",
282 m.connect("formatted_admin_edit_setting",
271 "/settings/{setting_id}.{format}/edit",
283 "/settings/{setting_id}.{format}/edit",
272 action="edit", conditions=dict(method=["GET"]))
284 action="edit", conditions=dict(method=["GET"]))
273 m.connect("admin_setting", "/settings/{setting_id}",
285 m.connect("admin_setting", "/settings/{setting_id}",
274 action="show", conditions=dict(method=["GET"]))
286 action="show", conditions=dict(method=["GET"]))
275 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
287 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
276 action="show", conditions=dict(method=["GET"]))
288 action="show", conditions=dict(method=["GET"]))
277 m.connect("admin_settings_my_account", "/my_account",
289 m.connect("admin_settings_my_account", "/my_account",
278 action="my_account", conditions=dict(method=["GET"]))
290 action="my_account", conditions=dict(method=["GET"]))
279 m.connect("admin_settings_my_account_update", "/my_account_update",
291 m.connect("admin_settings_my_account_update", "/my_account_update",
280 action="my_account_update", conditions=dict(method=["PUT"]))
292 action="my_account_update", conditions=dict(method=["PUT"]))
281 m.connect("admin_settings_create_repository", "/create_repository",
293 m.connect("admin_settings_create_repository", "/create_repository",
282 action="create_repository", conditions=dict(method=["GET"]))
294 action="create_repository", conditions=dict(method=["GET"]))
283
295
284 #NOTIFICATION REST ROUTES
296 #NOTIFICATION REST ROUTES
285 with rmap.submapper(path_prefix=ADMIN_PREFIX,
297 with rmap.submapper(path_prefix=ADMIN_PREFIX,
286 controller='admin/notifications') as m:
298 controller='admin/notifications') as m:
287 m.connect("notifications", "/notifications",
299 m.connect("notifications", "/notifications",
288 action="create", conditions=dict(method=["POST"]))
300 action="create", conditions=dict(method=["POST"]))
289 m.connect("notifications", "/notifications",
301 m.connect("notifications", "/notifications",
290 action="index", conditions=dict(method=["GET"]))
302 action="index", conditions=dict(method=["GET"]))
291 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
303 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
292 action="mark_all_read", conditions=dict(method=["GET"]))
304 action="mark_all_read", conditions=dict(method=["GET"]))
293 m.connect("formatted_notifications", "/notifications.{format}",
305 m.connect("formatted_notifications", "/notifications.{format}",
294 action="index", conditions=dict(method=["GET"]))
306 action="index", conditions=dict(method=["GET"]))
295 m.connect("new_notification", "/notifications/new",
307 m.connect("new_notification", "/notifications/new",
296 action="new", conditions=dict(method=["GET"]))
308 action="new", conditions=dict(method=["GET"]))
297 m.connect("formatted_new_notification", "/notifications/new.{format}",
309 m.connect("formatted_new_notification", "/notifications/new.{format}",
298 action="new", conditions=dict(method=["GET"]))
310 action="new", conditions=dict(method=["GET"]))
299 m.connect("/notification/{notification_id}",
311 m.connect("/notification/{notification_id}",
300 action="update", conditions=dict(method=["PUT"]))
312 action="update", conditions=dict(method=["PUT"]))
301 m.connect("/notification/{notification_id}",
313 m.connect("/notification/{notification_id}",
302 action="delete", conditions=dict(method=["DELETE"]))
314 action="delete", conditions=dict(method=["DELETE"]))
303 m.connect("edit_notification", "/notification/{notification_id}/edit",
315 m.connect("edit_notification", "/notification/{notification_id}/edit",
304 action="edit", conditions=dict(method=["GET"]))
316 action="edit", conditions=dict(method=["GET"]))
305 m.connect("formatted_edit_notification",
317 m.connect("formatted_edit_notification",
306 "/notification/{notification_id}.{format}/edit",
318 "/notification/{notification_id}.{format}/edit",
307 action="edit", conditions=dict(method=["GET"]))
319 action="edit", conditions=dict(method=["GET"]))
308 m.connect("notification", "/notification/{notification_id}",
320 m.connect("notification", "/notification/{notification_id}",
309 action="show", conditions=dict(method=["GET"]))
321 action="show", conditions=dict(method=["GET"]))
310 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
322 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
311 action="show", conditions=dict(method=["GET"]))
323 action="show", conditions=dict(method=["GET"]))
312
324
313
314
315 #ADMIN MAIN PAGES
325 #ADMIN MAIN PAGES
316 with rmap.submapper(path_prefix=ADMIN_PREFIX,
326 with rmap.submapper(path_prefix=ADMIN_PREFIX,
317 controller='admin/admin') as m:
327 controller='admin/admin') as m:
318 m.connect('admin_home', '', action='index')
328 m.connect('admin_home', '', action='index')
319 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
329 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
320 action='add_repo')
330 action='add_repo')
321
331
322 #==========================================================================
332 #==========================================================================
323 # API V1
333 # API V2
324 #==========================================================================
334 #==========================================================================
325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
335 with rmap.submapper(path_prefix=ADMIN_PREFIX,
326 controller='api/api') as m:
336 controller='api/api') as m:
327 m.connect('api', '/api')
337 m.connect('api', '/api')
328
338
329
330 #USER JOURNAL
339 #USER JOURNAL
331 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
340 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
332
341
333 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
342 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
334 controller='journal', action="public_journal")
343 controller='journal', action="public_journal")
335
344
336 rmap.connect('public_journal_rss', '%s/public_journal_rss' % ADMIN_PREFIX,
345 rmap.connect('public_journal_rss', '%s/public_journal_rss' % ADMIN_PREFIX,
337 controller='journal', action="public_journal_rss")
346 controller='journal', action="public_journal_rss")
338
347
339 rmap.connect('public_journal_atom',
348 rmap.connect('public_journal_atom',
340 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
349 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
341 action="public_journal_atom")
350 action="public_journal_atom")
342
351
343 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
352 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
344 controller='journal', action='toggle_following',
353 controller='journal', action='toggle_following',
345 conditions=dict(method=["POST"]))
354 conditions=dict(method=["POST"]))
346
355
347 #SEARCH
356 #SEARCH
348 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
357 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
349 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
358 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
350 controller='search')
359 controller='search')
351
360
352 #LOGIN/LOGOUT/REGISTER/SIGN IN
361 #LOGIN/LOGOUT/REGISTER/SIGN IN
353 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
362 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
354 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
363 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
355 action='logout')
364 action='logout')
356
365
357 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
366 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
358 action='register')
367 action='register')
359
368
360 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
369 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
361 controller='login', action='password_reset')
370 controller='login', action='password_reset')
362
371
363 rmap.connect('reset_password_confirmation',
372 rmap.connect('reset_password_confirmation',
364 '%s/password_reset_confirmation' % ADMIN_PREFIX,
373 '%s/password_reset_confirmation' % ADMIN_PREFIX,
365 controller='login', action='password_reset_confirmation')
374 controller='login', action='password_reset_confirmation')
366
375
367 #FEEDS
376 #FEEDS
368 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
377 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
369 controller='feed', action='rss',
378 controller='feed', action='rss',
370 conditions=dict(function=check_repo))
379 conditions=dict(function=check_repo))
371
380
372 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
381 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
373 controller='feed', action='atom',
382 controller='feed', action='atom',
374 conditions=dict(function=check_repo))
383 conditions=dict(function=check_repo))
375
384
376 #==========================================================================
385 #==========================================================================
377 # REPOSITORY ROUTES
386 # REPOSITORY ROUTES
378 #==========================================================================
387 #==========================================================================
379 rmap.connect('summary_home', '/{repo_name:.*}',
388 rmap.connect('summary_home', '/{repo_name:.*}',
380 controller='summary',
389 controller='summary',
381 conditions=dict(function=check_repo))
390 conditions=dict(function=check_repo))
382
391
383 rmap.connect('repos_group_home', '/{group_name:.*}',
392 rmap.connect('repos_group_home', '/{group_name:.*}',
384 controller='admin/repos_groups', action="show_by_name",
393 controller='admin/repos_groups', action="show_by_name",
385 conditions=dict(function=check_group))
394 conditions=dict(function=check_group))
386
395
387 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
396 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
388 controller='changeset', revision='tip',
397 controller='changeset', revision='tip',
389 conditions=dict(function=check_repo))
398 conditions=dict(function=check_repo))
390
399
391 rmap.connect('changeset_comment', '/{repo_name:.*}/changeset/{revision}/comment',
400 rmap.connect('changeset_comment',
401 '/{repo_name:.*}/changeset/{revision}/comment',
392 controller='changeset', revision='tip', action='comment',
402 controller='changeset', revision='tip', action='comment',
393 conditions=dict(function=check_repo))
403 conditions=dict(function=check_repo))
394
404
395 rmap.connect('changeset_comment_delete', '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
405 rmap.connect('changeset_comment_delete',
406 '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
396 controller='changeset', action='delete_comment',
407 controller='changeset', action='delete_comment',
397 conditions=dict(function=check_repo, method=["DELETE"]))
408 conditions=dict(function=check_repo, method=["DELETE"]))
398
409
399 rmap.connect('raw_changeset_home',
410 rmap.connect('raw_changeset_home',
400 '/{repo_name:.*}/raw-changeset/{revision}',
411 '/{repo_name:.*}/raw-changeset/{revision}',
401 controller='changeset', action='raw_changeset',
412 controller='changeset', action='raw_changeset',
402 revision='tip', conditions=dict(function=check_repo))
413 revision='tip', conditions=dict(function=check_repo))
403
414
404 rmap.connect('summary_home', '/{repo_name:.*}/summary',
415 rmap.connect('summary_home', '/{repo_name:.*}/summary',
405 controller='summary', conditions=dict(function=check_repo))
416 controller='summary', conditions=dict(function=check_repo))
406
417
407 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
418 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
408 controller='shortlog', conditions=dict(function=check_repo))
419 controller='shortlog', conditions=dict(function=check_repo))
409
420
410 rmap.connect('branches_home', '/{repo_name:.*}/branches',
421 rmap.connect('branches_home', '/{repo_name:.*}/branches',
411 controller='branches', conditions=dict(function=check_repo))
422 controller='branches', conditions=dict(function=check_repo))
412
423
413 rmap.connect('tags_home', '/{repo_name:.*}/tags',
424 rmap.connect('tags_home', '/{repo_name:.*}/tags',
414 controller='tags', conditions=dict(function=check_repo))
425 controller='tags', conditions=dict(function=check_repo))
415
426
416 rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
427 rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
417 controller='bookmarks', conditions=dict(function=check_repo))
428 controller='bookmarks', conditions=dict(function=check_repo))
418
429
419 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
430 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
420 controller='changelog', conditions=dict(function=check_repo))
431 controller='changelog', conditions=dict(function=check_repo))
421
432
422 rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
433 rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
423 controller='changelog', action='changelog_details',
434 controller='changelog', action='changelog_details',
424 conditions=dict(function=check_repo))
435 conditions=dict(function=check_repo))
425
436
426 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
437 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
427 controller='files', revision='tip', f_path='',
438 controller='files', revision='tip', f_path='',
428 conditions=dict(function=check_repo))
439 conditions=dict(function=check_repo))
429
440
430 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
441 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
431 controller='files', action='diff', revision='tip', f_path='',
442 controller='files', action='diff', revision='tip', f_path='',
432 conditions=dict(function=check_repo))
443 conditions=dict(function=check_repo))
433
444
434 rmap.connect('files_rawfile_home',
445 rmap.connect('files_rawfile_home',
435 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
446 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
436 controller='files', action='rawfile', revision='tip',
447 controller='files', action='rawfile', revision='tip',
437 f_path='', conditions=dict(function=check_repo))
448 f_path='', conditions=dict(function=check_repo))
438
449
439 rmap.connect('files_raw_home',
450 rmap.connect('files_raw_home',
440 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
451 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
441 controller='files', action='raw', revision='tip', f_path='',
452 controller='files', action='raw', revision='tip', f_path='',
442 conditions=dict(function=check_repo))
453 conditions=dict(function=check_repo))
443
454
444 rmap.connect('files_annotate_home',
455 rmap.connect('files_annotate_home',
445 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
456 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
446 controller='files', action='annotate', revision='tip',
457 controller='files', action='annotate', revision='tip',
447 f_path='', conditions=dict(function=check_repo))
458 f_path='', conditions=dict(function=check_repo))
448
459
449 rmap.connect('files_edit_home',
460 rmap.connect('files_edit_home',
450 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
461 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
451 controller='files', action='edit', revision='tip',
462 controller='files', action='edit', revision='tip',
452 f_path='', conditions=dict(function=check_repo))
463 f_path='', conditions=dict(function=check_repo))
453
464
454 rmap.connect('files_add_home',
465 rmap.connect('files_add_home',
455 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
466 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
456 controller='files', action='add', revision='tip',
467 controller='files', action='add', revision='tip',
457 f_path='', conditions=dict(function=check_repo))
468 f_path='', conditions=dict(function=check_repo))
458
469
459 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
470 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
460 controller='files', action='archivefile',
471 controller='files', action='archivefile',
461 conditions=dict(function=check_repo))
472 conditions=dict(function=check_repo))
462
473
463 rmap.connect('files_nodelist_home',
474 rmap.connect('files_nodelist_home',
464 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
475 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
465 controller='files', action='nodelist',
476 controller='files', action='nodelist',
466 conditions=dict(function=check_repo))
477 conditions=dict(function=check_repo))
467
478
468 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
479 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
469 controller='settings', action="delete",
480 controller='settings', action="delete",
470 conditions=dict(method=["DELETE"], function=check_repo))
481 conditions=dict(method=["DELETE"], function=check_repo))
471
482
472 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
483 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
473 controller='settings', action="update",
484 controller='settings', action="update",
474 conditions=dict(method=["PUT"], function=check_repo))
485 conditions=dict(method=["PUT"], function=check_repo))
475
486
476 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
487 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
477 controller='settings', action='index',
488 controller='settings', action='index',
478 conditions=dict(function=check_repo))
489 conditions=dict(function=check_repo))
479
490
480 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
491 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
481 controller='forks', action='fork_create',
492 controller='forks', action='fork_create',
482 conditions=dict(function=check_repo, method=["POST"]))
493 conditions=dict(function=check_repo, method=["POST"]))
483
494
484 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
495 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
485 controller='forks', action='fork',
496 controller='forks', action='fork',
486 conditions=dict(function=check_repo))
497 conditions=dict(function=check_repo))
487
498
488 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
499 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
489 controller='forks', action='forks',
500 controller='forks', action='forks',
490 conditions=dict(function=check_repo))
501 conditions=dict(function=check_repo))
491
502
492 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
503 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
493 controller='followers', action='followers',
504 controller='followers', action='followers',
494 conditions=dict(function=check_repo))
505 conditions=dict(function=check_repo))
495
506
496
497 return rmap
507 return rmap
@@ -1,431 +1,433 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos
3 rhodecode.controllers.admin.repos
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Admin controller for RhodeCode
6 Repositories controller for RhodeCode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from paste.httpexceptions import HTTPInternalServerError
31 from paste.httpexceptions import HTTPInternalServerError
32 from pylons import request, session, tmpl_context as c, url
32 from pylons import request, session, tmpl_context as c, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.exc import IntegrityError
35 from sqlalchemy.exc import IntegrityError
36
36
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
39 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
40 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 from rhodecode.lib.helpers import get_token
42 from rhodecode.lib.helpers import get_token
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
44 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
45 from rhodecode.model.forms import RepoForm
45 from rhodecode.model.forms import RepoForm
46 from rhodecode.model.scm import ScmModel
46 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class ReposController(BaseController):
52 class ReposController(BaseController):
53 """
53 """
54 REST Controller styled on the Atom Publishing Protocol"""
54 REST Controller styled on the Atom Publishing Protocol"""
55 # To properly map this controller, ensure your config/routing.py
55 # To properly map this controller, ensure your config/routing.py
56 # file has a resource setup:
56 # file has a resource setup:
57 # map.resource('repo', 'repos')
57 # map.resource('repo', 'repos')
58
58
59 @LoginRequired()
59 @LoginRequired()
60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 def __before__(self):
61 def __before__(self):
62 c.admin_user = session.get('admin_user')
62 c.admin_user = session.get('admin_user')
63 c.admin_username = session.get('admin_username')
63 c.admin_username = session.get('admin_username')
64 super(ReposController, self).__before__()
64 super(ReposController, self).__before__()
65
65
66 def __load_defaults(self):
66 def __load_defaults(self):
67 c.repo_groups = RepoGroup.groups_choices()
67 c.repo_groups = RepoGroup.groups_choices()
68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
69
69
70 repo_model = RepoModel()
70 repo_model = RepoModel()
71 c.users_array = repo_model.get_users_js()
71 c.users_array = repo_model.get_users_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
73
73
74 def __load_data(self, repo_name=None):
74 def __load_data(self, repo_name=None):
75 """
75 """
76 Load defaults settings for edit, and update
76 Load defaults settings for edit, and update
77
77
78 :param repo_name:
78 :param repo_name:
79 """
79 """
80 self.__load_defaults()
80 self.__load_defaults()
81
81
82 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
82 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
83 repo = db_repo.scm_instance
83 repo = db_repo.scm_instance
84
84
85 if c.repo_info is None:
85 if c.repo_info is None:
86 h.flash(_('%s repository is not mapped to db perhaps'
86 h.flash(_('%s repository is not mapped to db perhaps'
87 ' it was created or renamed from the filesystem'
87 ' it was created or renamed from the filesystem'
88 ' please run the application again'
88 ' please run the application again'
89 ' in order to rescan repositories') % repo_name,
89 ' in order to rescan repositories') % repo_name,
90 category='error')
90 category='error')
91
91
92 return redirect(url('repos'))
92 return redirect(url('repos'))
93
93
94 c.default_user_id = User.get_by_username('default').user_id
94 c.default_user_id = User.get_by_username('default').user_id
95 c.in_public_journal = UserFollowing.query()\
95 c.in_public_journal = UserFollowing.query()\
96 .filter(UserFollowing.user_id == c.default_user_id)\
96 .filter(UserFollowing.user_id == c.default_user_id)\
97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
98
98
99 if c.repo_info.stats:
99 if c.repo_info.stats:
100 # this is on what revision we ended up so we add +1 for count
100 # this is on what revision we ended up so we add +1 for count
101 last_rev = c.repo_info.stats.stat_on_revision + 1
101 last_rev = c.repo_info.stats.stat_on_revision + 1
102 else:
102 else:
103 last_rev = 0
103 last_rev = 0
104 c.stats_revision = last_rev
104 c.stats_revision = last_rev
105
105
106 c.repo_last_rev = repo.count() if repo.revisions else 0
106 c.repo_last_rev = repo.count() if repo.revisions else 0
107
107
108 if last_rev == 0 or c.repo_last_rev == 0:
108 if last_rev == 0 or c.repo_last_rev == 0:
109 c.stats_percentage = 0
109 c.stats_percentage = 0
110 else:
110 else:
111 c.stats_percentage = '%.2f' % ((float((last_rev)) /
111 c.stats_percentage = '%.2f' % ((float((last_rev)) /
112 c.repo_last_rev) * 100)
112 c.repo_last_rev) * 100)
113
113
114 defaults = RepoModel()._get_defaults(repo_name)
114 defaults = RepoModel()._get_defaults(repo_name)
115
115
116 c.repos_list = [('', _('--REMOVE FORK--'))]
116 c.repos_list = [('', _('--REMOVE FORK--'))]
117 c.repos_list += [(x.repo_id, x.repo_name) for x in
117 c.repos_list += [(x.repo_id, x.repo_name) for x in
118 Repository.query().order_by(Repository.repo_name).all()]
118 Repository.query().order_by(Repository.repo_name).all()]
119 return defaults
119 return defaults
120
120
121 @HasPermissionAllDecorator('hg.admin')
121 @HasPermissionAllDecorator('hg.admin')
122 def index(self, format='html'):
122 def index(self, format='html'):
123 """GET /repos: All items in the collection"""
123 """GET /repos: All items in the collection"""
124 # url('repos')
124 # url('repos')
125
125
126 c.repos_list = ScmModel().get_repos(Repository.query()
126 c.repos_list = ScmModel().get_repos(Repository.query()
127 .order_by(Repository.repo_name)
127 .order_by(Repository.repo_name)
128 .all(), sort_key='name_sort')
128 .all(), sort_key='name_sort')
129 return render('admin/repos/repos.html')
129 return render('admin/repos/repos.html')
130
130
131 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
131 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
132 def create(self):
132 def create(self):
133 """
133 """
134 POST /repos: Create a new item"""
134 POST /repos: Create a new item"""
135 # url('repos')
135 # url('repos')
136
136
137 self.__load_defaults()
137 self.__load_defaults()
138 form_result = {}
138 form_result = {}
139 try:
139 try:
140 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
140 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
141 .to_python(dict(request.POST))
141 .to_python(dict(request.POST))
142 RepoModel().create(form_result, self.rhodecode_user)
142 RepoModel().create(form_result, self.rhodecode_user)
143 if form_result['clone_uri']:
143 if form_result['clone_uri']:
144 h.flash(_('created repository %s from %s') \
144 h.flash(_('created repository %s from %s') \
145 % (form_result['repo_name'], form_result['clone_uri']),
145 % (form_result['repo_name'], form_result['clone_uri']),
146 category='success')
146 category='success')
147 else:
147 else:
148 h.flash(_('created repository %s') % form_result['repo_name'],
148 h.flash(_('created repository %s') % form_result['repo_name'],
149 category='success')
149 category='success')
150
150
151 if request.POST.get('user_created'):
151 if request.POST.get('user_created'):
152 # created by regular non admin user
152 # created by regular non admin user
153 action_logger(self.rhodecode_user, 'user_created_repo',
153 action_logger(self.rhodecode_user, 'user_created_repo',
154 form_result['repo_name_full'], '', self.sa)
154 form_result['repo_name_full'], '', self.sa)
155 else:
155 else:
156 action_logger(self.rhodecode_user, 'admin_created_repo',
156 action_logger(self.rhodecode_user, 'admin_created_repo',
157 form_result['repo_name_full'], '', self.sa)
157 form_result['repo_name_full'], '', self.sa)
158 Session.commit()
158 Session.commit()
159 except formencode.Invalid, errors:
159 except formencode.Invalid, errors:
160
160
161 c.new_repo = errors.value['repo_name']
161 c.new_repo = errors.value['repo_name']
162
162
163 if request.POST.get('user_created'):
163 if request.POST.get('user_created'):
164 r = render('admin/repos/repo_add_create_repository.html')
164 r = render('admin/repos/repo_add_create_repository.html')
165 else:
165 else:
166 r = render('admin/repos/repo_add.html')
166 r = render('admin/repos/repo_add.html')
167
167
168 return htmlfill.render(
168 return htmlfill.render(
169 r,
169 r,
170 defaults=errors.value,
170 defaults=errors.value,
171 errors=errors.error_dict or {},
171 errors=errors.error_dict or {},
172 prefix_error=False,
172 prefix_error=False,
173 encoding="UTF-8")
173 encoding="UTF-8")
174
174
175 except Exception:
175 except Exception:
176 log.error(traceback.format_exc())
176 log.error(traceback.format_exc())
177 msg = _('error occurred during creation of repository %s') \
177 msg = _('error occurred during creation of repository %s') \
178 % form_result.get('repo_name')
178 % form_result.get('repo_name')
179 h.flash(msg, category='error')
179 h.flash(msg, category='error')
180 if request.POST.get('user_created'):
180 if request.POST.get('user_created'):
181 return redirect(url('home'))
181 return redirect(url('home'))
182 return redirect(url('repos'))
182 return redirect(url('repos'))
183
183
184 @HasPermissionAllDecorator('hg.admin')
184 @HasPermissionAllDecorator('hg.admin')
185 def new(self, format='html'):
185 def new(self, format='html'):
186 """GET /repos/new: Form to create a new item"""
186 """GET /repos/new: Form to create a new item"""
187 new_repo = request.GET.get('repo', '')
187 new_repo = request.GET.get('repo', '')
188 c.new_repo = repo_name_slug(new_repo)
188 c.new_repo = repo_name_slug(new_repo)
189 self.__load_defaults()
189 self.__load_defaults()
190 return render('admin/repos/repo_add.html')
190 return render('admin/repos/repo_add.html')
191
191
192 @HasPermissionAllDecorator('hg.admin')
192 @HasPermissionAllDecorator('hg.admin')
193 def update(self, repo_name):
193 def update(self, repo_name):
194 """
194 """
195 PUT /repos/repo_name: Update an existing item"""
195 PUT /repos/repo_name: Update an existing item"""
196 # Forms posted to this method should contain a hidden field:
196 # Forms posted to this method should contain a hidden field:
197 # <input type="hidden" name="_method" value="PUT" />
197 # <input type="hidden" name="_method" value="PUT" />
198 # Or using helpers:
198 # Or using helpers:
199 # h.form(url('repo', repo_name=ID),
199 # h.form(url('repo', repo_name=ID),
200 # method='put')
200 # method='put')
201 # url('repo', repo_name=ID)
201 # url('repo', repo_name=ID)
202 self.__load_defaults()
202 self.__load_defaults()
203 repo_model = RepoModel()
203 repo_model = RepoModel()
204 changed_name = repo_name
204 changed_name = repo_name
205 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
205 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
206 repo_groups=c.repo_groups_choices)()
206 repo_groups=c.repo_groups_choices)()
207 try:
207 try:
208 form_result = _form.to_python(dict(request.POST))
208 form_result = _form.to_python(dict(request.POST))
209 repo = repo_model.update(repo_name, form_result)
209 repo = repo_model.update(repo_name, form_result)
210 invalidate_cache('get_repo_cached_%s' % repo_name)
210 invalidate_cache('get_repo_cached_%s' % repo_name)
211 h.flash(_('Repository %s updated successfully' % repo_name),
211 h.flash(_('Repository %s updated successfully' % repo_name),
212 category='success')
212 category='success')
213 changed_name = repo.repo_name
213 changed_name = repo.repo_name
214 action_logger(self.rhodecode_user, 'admin_updated_repo',
214 action_logger(self.rhodecode_user, 'admin_updated_repo',
215 changed_name, '', self.sa)
215 changed_name, '', self.sa)
216 Session.commit()
216 Session.commit()
217 except formencode.Invalid, errors:
217 except formencode.Invalid, errors:
218 defaults = self.__load_data(repo_name)
218 defaults = self.__load_data(repo_name)
219 defaults.update(errors.value)
219 defaults.update(errors.value)
220 return htmlfill.render(
220 return htmlfill.render(
221 render('admin/repos/repo_edit.html'),
221 render('admin/repos/repo_edit.html'),
222 defaults=defaults,
222 defaults=defaults,
223 errors=errors.error_dict or {},
223 errors=errors.error_dict or {},
224 prefix_error=False,
224 prefix_error=False,
225 encoding="UTF-8")
225 encoding="UTF-8")
226
226
227 except Exception:
227 except Exception:
228 log.error(traceback.format_exc())
228 log.error(traceback.format_exc())
229 h.flash(_('error occurred during update of repository %s') \
229 h.flash(_('error occurred during update of repository %s') \
230 % repo_name, category='error')
230 % repo_name, category='error')
231 return redirect(url('edit_repo', repo_name=changed_name))
231 return redirect(url('edit_repo', repo_name=changed_name))
232
232
233 @HasPermissionAllDecorator('hg.admin')
233 @HasPermissionAllDecorator('hg.admin')
234 def delete(self, repo_name):
234 def delete(self, repo_name):
235 """
235 """
236 DELETE /repos/repo_name: Delete an existing item"""
236 DELETE /repos/repo_name: Delete an existing item"""
237 # Forms posted to this method should contain a hidden field:
237 # Forms posted to this method should contain a hidden field:
238 # <input type="hidden" name="_method" value="DELETE" />
238 # <input type="hidden" name="_method" value="DELETE" />
239 # Or using helpers:
239 # Or using helpers:
240 # h.form(url('repo', repo_name=ID),
240 # h.form(url('repo', repo_name=ID),
241 # method='delete')
241 # method='delete')
242 # url('repo', repo_name=ID)
242 # url('repo', repo_name=ID)
243
243
244 repo_model = RepoModel()
244 repo_model = RepoModel()
245 repo = repo_model.get_by_repo_name(repo_name)
245 repo = repo_model.get_by_repo_name(repo_name)
246 if not repo:
246 if not repo:
247 h.flash(_('%s repository is not mapped to db perhaps'
247 h.flash(_('%s repository is not mapped to db perhaps'
248 ' it was moved or renamed from the filesystem'
248 ' it was moved or renamed from the filesystem'
249 ' please run the application again'
249 ' please run the application again'
250 ' in order to rescan repositories') % repo_name,
250 ' in order to rescan repositories') % repo_name,
251 category='error')
251 category='error')
252
252
253 return redirect(url('repos'))
253 return redirect(url('repos'))
254 try:
254 try:
255 action_logger(self.rhodecode_user, 'admin_deleted_repo',
255 action_logger(self.rhodecode_user, 'admin_deleted_repo',
256 repo_name, '', self.sa)
256 repo_name, '', self.sa)
257 repo_model.delete(repo)
257 repo_model.delete(repo)
258 invalidate_cache('get_repo_cached_%s' % repo_name)
258 invalidate_cache('get_repo_cached_%s' % repo_name)
259 h.flash(_('deleted repository %s') % repo_name, category='success')
259 h.flash(_('deleted repository %s') % repo_name, category='success')
260 Session.commit()
260 Session.commit()
261 except IntegrityError, e:
261 except IntegrityError, e:
262 if e.message.find('repositories_fork_id_fkey') != -1:
262 if e.message.find('repositories_fork_id_fkey') != -1:
263 log.error(traceback.format_exc())
263 log.error(traceback.format_exc())
264 h.flash(_('Cannot delete %s it still contains attached '
264 h.flash(_('Cannot delete %s it still contains attached '
265 'forks') % repo_name,
265 'forks') % repo_name,
266 category='warning')
266 category='warning')
267 else:
267 else:
268 log.error(traceback.format_exc())
268 log.error(traceback.format_exc())
269 h.flash(_('An error occurred during '
269 h.flash(_('An error occurred during '
270 'deletion of %s') % repo_name,
270 'deletion of %s') % repo_name,
271 category='error')
271 category='error')
272
272
273 except Exception, e:
273 except Exception, e:
274 log.error(traceback.format_exc())
274 log.error(traceback.format_exc())
275 h.flash(_('An error occurred during deletion of %s') % repo_name,
275 h.flash(_('An error occurred during deletion of %s') % repo_name,
276 category='error')
276 category='error')
277
277
278 return redirect(url('repos'))
278 return redirect(url('repos'))
279
279
280
281 @HasRepoPermissionAllDecorator('repository.admin')
280 @HasRepoPermissionAllDecorator('repository.admin')
282 def delete_perm_user(self, repo_name):
281 def delete_perm_user(self, repo_name):
283 """
282 """
284 DELETE an existing repository permission user
283 DELETE an existing repository permission user
285
284
286 :param repo_name:
285 :param repo_name:
287 """
286 """
288
287
289 try:
288 try:
290 repo_model = RepoModel()
289 RepoModel().revoke_user_permission(repo=repo_name,
291 repo_model.delete_perm_user(request.POST, repo_name)
290 user=request.POST['user_id'])
292 Session.commit()
291 Session.commit()
293 except Exception, e:
292 except Exception:
293 log.error(traceback.format_exc())
294 h.flash(_('An error occurred during deletion of repository user'),
294 h.flash(_('An error occurred during deletion of repository user'),
295 category='error')
295 category='error')
296 raise HTTPInternalServerError()
296 raise HTTPInternalServerError()
297
297
298 @HasRepoPermissionAllDecorator('repository.admin')
298 @HasRepoPermissionAllDecorator('repository.admin')
299 def delete_perm_users_group(self, repo_name):
299 def delete_perm_users_group(self, repo_name):
300 """
300 """
301 DELETE an existing repository permission users group
301 DELETE an existing repository permission users group
302
302
303 :param repo_name:
303 :param repo_name:
304 """
304 """
305
305 try:
306 try:
306 repo_model = RepoModel()
307 RepoModel().revoke_users_group_permission(
307 repo_model.delete_perm_users_group(request.POST, repo_name)
308 repo=repo_name, group_name=request.POST['users_group_id']
309 )
308 Session.commit()
310 Session.commit()
309 except Exception, e:
311 except Exception:
312 log.error(traceback.format_exc())
310 h.flash(_('An error occurred during deletion of repository'
313 h.flash(_('An error occurred during deletion of repository'
311 ' users groups'),
314 ' users groups'),
312 category='error')
315 category='error')
313 raise HTTPInternalServerError()
316 raise HTTPInternalServerError()
314
317
315 @HasPermissionAllDecorator('hg.admin')
318 @HasPermissionAllDecorator('hg.admin')
316 def repo_stats(self, repo_name):
319 def repo_stats(self, repo_name):
317 """
320 """
318 DELETE an existing repository statistics
321 DELETE an existing repository statistics
319
322
320 :param repo_name:
323 :param repo_name:
321 """
324 """
322
325
323 try:
326 try:
324 repo_model = RepoModel()
327 RepoModel().delete_stats(repo_name)
325 repo_model.delete_stats(repo_name)
326 Session.commit()
328 Session.commit()
327 except Exception, e:
329 except Exception, e:
328 h.flash(_('An error occurred during deletion of repository stats'),
330 h.flash(_('An error occurred during deletion of repository stats'),
329 category='error')
331 category='error')
330 return redirect(url('edit_repo', repo_name=repo_name))
332 return redirect(url('edit_repo', repo_name=repo_name))
331
333
332 @HasPermissionAllDecorator('hg.admin')
334 @HasPermissionAllDecorator('hg.admin')
333 def repo_cache(self, repo_name):
335 def repo_cache(self, repo_name):
334 """
336 """
335 INVALIDATE existing repository cache
337 INVALIDATE existing repository cache
336
338
337 :param repo_name:
339 :param repo_name:
338 """
340 """
339
341
340 try:
342 try:
341 ScmModel().mark_for_invalidation(repo_name)
343 ScmModel().mark_for_invalidation(repo_name)
342 Session.commit()
344 Session.commit()
343 except Exception, e:
345 except Exception, e:
344 h.flash(_('An error occurred during cache invalidation'),
346 h.flash(_('An error occurred during cache invalidation'),
345 category='error')
347 category='error')
346 return redirect(url('edit_repo', repo_name=repo_name))
348 return redirect(url('edit_repo', repo_name=repo_name))
347
349
348 @HasPermissionAllDecorator('hg.admin')
350 @HasPermissionAllDecorator('hg.admin')
349 def repo_public_journal(self, repo_name):
351 def repo_public_journal(self, repo_name):
350 """
352 """
351 Set's this repository to be visible in public journal,
353 Set's this repository to be visible in public journal,
352 in other words assing default user to follow this repo
354 in other words assing default user to follow this repo
353
355
354 :param repo_name:
356 :param repo_name:
355 """
357 """
356
358
357 cur_token = request.POST.get('auth_token')
359 cur_token = request.POST.get('auth_token')
358 token = get_token()
360 token = get_token()
359 if cur_token == token:
361 if cur_token == token:
360 try:
362 try:
361 repo_id = Repository.get_by_repo_name(repo_name).repo_id
363 repo_id = Repository.get_by_repo_name(repo_name).repo_id
362 user_id = User.get_by_username('default').user_id
364 user_id = User.get_by_username('default').user_id
363 self.scm_model.toggle_following_repo(repo_id, user_id)
365 self.scm_model.toggle_following_repo(repo_id, user_id)
364 h.flash(_('Updated repository visibility in public journal'),
366 h.flash(_('Updated repository visibility in public journal'),
365 category='success')
367 category='success')
366 Session.commit()
368 Session.commit()
367 except:
369 except:
368 h.flash(_('An error occurred during setting this'
370 h.flash(_('An error occurred during setting this'
369 ' repository in public journal'),
371 ' repository in public journal'),
370 category='error')
372 category='error')
371
373
372 else:
374 else:
373 h.flash(_('Token mismatch'), category='error')
375 h.flash(_('Token mismatch'), category='error')
374 return redirect(url('edit_repo', repo_name=repo_name))
376 return redirect(url('edit_repo', repo_name=repo_name))
375
377
376 @HasPermissionAllDecorator('hg.admin')
378 @HasPermissionAllDecorator('hg.admin')
377 def repo_pull(self, repo_name):
379 def repo_pull(self, repo_name):
378 """
380 """
379 Runs task to update given repository with remote changes,
381 Runs task to update given repository with remote changes,
380 ie. make pull on remote location
382 ie. make pull on remote location
381
383
382 :param repo_name:
384 :param repo_name:
383 """
385 """
384 try:
386 try:
385 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
387 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
386 h.flash(_('Pulled from remote location'), category='success')
388 h.flash(_('Pulled from remote location'), category='success')
387 except Exception, e:
389 except Exception, e:
388 h.flash(_('An error occurred during pull from remote location'),
390 h.flash(_('An error occurred during pull from remote location'),
389 category='error')
391 category='error')
390
392
391 return redirect(url('edit_repo', repo_name=repo_name))
393 return redirect(url('edit_repo', repo_name=repo_name))
392
394
393 @HasPermissionAllDecorator('hg.admin')
395 @HasPermissionAllDecorator('hg.admin')
394 def repo_as_fork(self, repo_name):
396 def repo_as_fork(self, repo_name):
395 """
397 """
396 Mark given repository as a fork of another
398 Mark given repository as a fork of another
397
399
398 :param repo_name:
400 :param repo_name:
399 """
401 """
400 try:
402 try:
401 fork_id = request.POST.get('id_fork_of')
403 fork_id = request.POST.get('id_fork_of')
402 repo = ScmModel().mark_as_fork(repo_name, fork_id,
404 repo = ScmModel().mark_as_fork(repo_name, fork_id,
403 self.rhodecode_user.username)
405 self.rhodecode_user.username)
404 fork = repo.fork.repo_name if repo.fork else _('Nothing')
406 fork = repo.fork.repo_name if repo.fork else _('Nothing')
405 Session.commit()
407 Session.commit()
406 h.flash(_('Marked repo %s as fork of %s' % (repo_name,fork)),
408 h.flash(_('Marked repo %s as fork of %s' % (repo_name,fork)),
407 category='success')
409 category='success')
408 except Exception, e:
410 except Exception, e:
409 raise
411 raise
410 h.flash(_('An error occurred during this operation'),
412 h.flash(_('An error occurred during this operation'),
411 category='error')
413 category='error')
412
414
413 return redirect(url('edit_repo', repo_name=repo_name))
415 return redirect(url('edit_repo', repo_name=repo_name))
414
416
415 @HasPermissionAllDecorator('hg.admin')
417 @HasPermissionAllDecorator('hg.admin')
416 def show(self, repo_name, format='html'):
418 def show(self, repo_name, format='html'):
417 """GET /repos/repo_name: Show a specific item"""
419 """GET /repos/repo_name: Show a specific item"""
418 # url('repo', repo_name=ID)
420 # url('repo', repo_name=ID)
419
421
420 @HasPermissionAllDecorator('hg.admin')
422 @HasPermissionAllDecorator('hg.admin')
421 def edit(self, repo_name, format='html'):
423 def edit(self, repo_name, format='html'):
422 """GET /repos/repo_name/edit: Form to edit an existing item"""
424 """GET /repos/repo_name/edit: Form to edit an existing item"""
423 # url('edit_repo', repo_name=ID)
425 # url('edit_repo', repo_name=ID)
424 defaults = self.__load_data(repo_name)
426 defaults = self.__load_data(repo_name)
425
427
426 return htmlfill.render(
428 return htmlfill.render(
427 render('admin/repos/repo_edit.html'),
429 render('admin/repos/repo_edit.html'),
428 defaults=defaults,
430 defaults=defaults,
429 encoding="UTF-8",
431 encoding="UTF-8",
430 force_defaults=False
432 force_defaults=False
431 )
433 )
@@ -1,250 +1,313 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos_groups
3 rhodecode.controllers.admin.repos_groups
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 repos groups controller for RhodeCode
6 Repositories groups controller for RhodeCode
7
7
8 :created_on: Mar 23, 2010
8 :created_on: Mar 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29
29
30 from formencode import htmlfill
30 from formencode import htmlfill
31
31
32 from pylons import request, response, session, tmpl_context as c, url
32 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 from sqlalchemy.exc import IntegrityError
36 from sqlalchemy.exc import IntegrityError
37
37
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
40 HasReposGroupPermissionAnyDecorator
40 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.model.db import RepoGroup
42 from rhodecode.model.db import RepoGroup
42 from rhodecode.model.repos_group import ReposGroupModel
43 from rhodecode.model.repos_group import ReposGroupModel
43 from rhodecode.model.forms import ReposGroupForm
44 from rhodecode.model.forms import ReposGroupForm
44 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.repo import RepoModel
47 from webob.exc import HTTPInternalServerError
45
48
46 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
47
50
48
51
49 class ReposGroupsController(BaseController):
52 class ReposGroupsController(BaseController):
50 """REST Controller styled on the Atom Publishing Protocol"""
53 """REST Controller styled on the Atom Publishing Protocol"""
51 # To properly map this controller, ensure your config/routing.py
54 # To properly map this controller, ensure your config/routing.py
52 # file has a resource setup:
55 # file has a resource setup:
53 # map.resource('repos_group', 'repos_groups')
56 # map.resource('repos_group', 'repos_groups')
54
57
55 @LoginRequired()
58 @LoginRequired()
56 def __before__(self):
59 def __before__(self):
57 super(ReposGroupsController, self).__before__()
60 super(ReposGroupsController, self).__before__()
58
61
59 def __load_defaults(self):
62 def __load_defaults(self):
60 c.repo_groups = RepoGroup.groups_choices()
63 c.repo_groups = RepoGroup.groups_choices()
61 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
64 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
62
65
66 repo_model = RepoModel()
67 c.users_array = repo_model.get_users_js()
68 c.users_groups_array = repo_model.get_users_groups_js()
69
63 def __load_data(self, group_id):
70 def __load_data(self, group_id):
64 """
71 """
65 Load defaults settings for edit, and update
72 Load defaults settings for edit, and update
66
73
67 :param group_id:
74 :param group_id:
68 """
75 """
69 self.__load_defaults()
76 self.__load_defaults()
70
77
71 repo_group = RepoGroup.get(group_id)
78 repo_group = RepoGroup.get(group_id)
72
79
73 data = repo_group.get_dict()
80 data = repo_group.get_dict()
74
81
75 data['group_name'] = repo_group.name
82 data['group_name'] = repo_group.name
76
83
84 # fill repository users
85 for p in repo_group.repo_group_to_perm:
86 data.update({'u_perm_%s' % p.user.username:
87 p.permission.permission_name})
88
89 # fill repository groups
90 for p in repo_group.users_group_to_perm:
91 data.update({'g_perm_%s' % p.users_group.users_group_name:
92 p.permission.permission_name})
93
77 return data
94 return data
78
95
79 @HasPermissionAnyDecorator('hg.admin')
96 @HasPermissionAnyDecorator('hg.admin')
80 def index(self, format='html'):
97 def index(self, format='html'):
81 """GET /repos_groups: All items in the collection"""
98 """GET /repos_groups: All items in the collection"""
82 # url('repos_groups')
99 # url('repos_groups')
83
84 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
100 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
85 c.groups = sorted(RepoGroup.query().all(), key=sk)
101 c.groups = sorted(RepoGroup.query().all(), key=sk)
86 return render('admin/repos_groups/repos_groups_show.html')
102 return render('admin/repos_groups/repos_groups_show.html')
87
103
88 @HasPermissionAnyDecorator('hg.admin')
104 @HasPermissionAnyDecorator('hg.admin')
89 def create(self):
105 def create(self):
90 """POST /repos_groups: Create a new item"""
106 """POST /repos_groups: Create a new item"""
91 # url('repos_groups')
107 # url('repos_groups')
92 self.__load_defaults()
108 self.__load_defaults()
93 repos_group_form = ReposGroupForm(available_groups =
109 repos_group_form = ReposGroupForm(available_groups =
94 c.repo_groups_choices)()
110 c.repo_groups_choices)()
95 try:
111 try:
96 form_result = repos_group_form.to_python(dict(request.POST))
112 form_result = repos_group_form.to_python(dict(request.POST))
97 ReposGroupModel().create(form_result)
113 ReposGroupModel().create(
114 group_name=form_result['group_name'],
115 group_description=form_result['group_description'],
116 parent=form_result['group_parent_id']
117 )
98 Session.commit()
118 Session.commit()
99 h.flash(_('created repos group %s') \
119 h.flash(_('created repos group %s') \
100 % form_result['group_name'], category='success')
120 % form_result['group_name'], category='success')
101 #TODO: in futureaction_logger(, '', '', '', self.sa)
121 #TODO: in futureaction_logger(, '', '', '', self.sa)
102 except formencode.Invalid, errors:
122 except formencode.Invalid, errors:
103
123
104 return htmlfill.render(
124 return htmlfill.render(
105 render('admin/repos_groups/repos_groups_add.html'),
125 render('admin/repos_groups/repos_groups_add.html'),
106 defaults=errors.value,
126 defaults=errors.value,
107 errors=errors.error_dict or {},
127 errors=errors.error_dict or {},
108 prefix_error=False,
128 prefix_error=False,
109 encoding="UTF-8")
129 encoding="UTF-8")
110 except Exception:
130 except Exception:
111 log.error(traceback.format_exc())
131 log.error(traceback.format_exc())
112 h.flash(_('error occurred during creation of repos group %s') \
132 h.flash(_('error occurred during creation of repos group %s') \
113 % request.POST.get('group_name'), category='error')
133 % request.POST.get('group_name'), category='error')
114
134
115 return redirect(url('repos_groups'))
135 return redirect(url('repos_groups'))
116
136
117 @HasPermissionAnyDecorator('hg.admin')
137 @HasPermissionAnyDecorator('hg.admin')
118 def new(self, format='html'):
138 def new(self, format='html'):
119 """GET /repos_groups/new: Form to create a new item"""
139 """GET /repos_groups/new: Form to create a new item"""
120 # url('new_repos_group')
140 # url('new_repos_group')
121 self.__load_defaults()
141 self.__load_defaults()
122 return render('admin/repos_groups/repos_groups_add.html')
142 return render('admin/repos_groups/repos_groups_add.html')
123
143
124 @HasPermissionAnyDecorator('hg.admin')
144 @HasPermissionAnyDecorator('hg.admin')
125 def update(self, id):
145 def update(self, id):
126 """PUT /repos_groups/id: Update an existing item"""
146 """PUT /repos_groups/id: Update an existing item"""
127 # Forms posted to this method should contain a hidden field:
147 # Forms posted to this method should contain a hidden field:
128 # <input type="hidden" name="_method" value="PUT" />
148 # <input type="hidden" name="_method" value="PUT" />
129 # Or using helpers:
149 # Or using helpers:
130 # h.form(url('repos_group', id=ID),
150 # h.form(url('repos_group', id=ID),
131 # method='put')
151 # method='put')
132 # url('repos_group', id=ID)
152 # url('repos_group', id=ID)
133
153
134 self.__load_defaults()
154 self.__load_defaults()
135 c.repos_group = RepoGroup.get(id)
155 c.repos_group = RepoGroup.get(id)
136
156
137 repos_group_form = ReposGroupForm(edit=True,
157 repos_group_form = ReposGroupForm(
138 old_data=c.repos_group.get_dict(),
158 edit=True,
139 available_groups=
159 old_data=c.repos_group.get_dict(),
140 c.repo_groups_choices)()
160 available_groups=c.repo_groups_choices
161 )()
141 try:
162 try:
142 form_result = repos_group_form.to_python(dict(request.POST))
163 form_result = repos_group_form.to_python(dict(request.POST))
143 ReposGroupModel().update(id, form_result)
164 ReposGroupModel().update(id, form_result)
144 Session.commit()
165 Session.commit()
145 h.flash(_('updated repos group %s') \
166 h.flash(_('updated repos group %s') \
146 % form_result['group_name'], category='success')
167 % form_result['group_name'], category='success')
147 #TODO: in futureaction_logger(, '', '', '', self.sa)
168 #TODO: in futureaction_logger(, '', '', '', self.sa)
148 except formencode.Invalid, errors:
169 except formencode.Invalid, errors:
149
170
150 return htmlfill.render(
171 return htmlfill.render(
151 render('admin/repos_groups/repos_groups_edit.html'),
172 render('admin/repos_groups/repos_groups_edit.html'),
152 defaults=errors.value,
173 defaults=errors.value,
153 errors=errors.error_dict or {},
174 errors=errors.error_dict or {},
154 prefix_error=False,
175 prefix_error=False,
155 encoding="UTF-8")
176 encoding="UTF-8")
156 except Exception:
177 except Exception:
157 log.error(traceback.format_exc())
178 log.error(traceback.format_exc())
158 h.flash(_('error occurred during update of repos group %s') \
179 h.flash(_('error occurred during update of repos group %s') \
159 % request.POST.get('group_name'), category='error')
180 % request.POST.get('group_name'), category='error')
160
181
161 return redirect(url('repos_groups'))
182 return redirect(url('repos_groups'))
162
183
163 @HasPermissionAnyDecorator('hg.admin')
184 @HasPermissionAnyDecorator('hg.admin')
164 def delete(self, id):
185 def delete(self, id):
165 """DELETE /repos_groups/id: Delete an existing item"""
186 """DELETE /repos_groups/id: Delete an existing item"""
166 # Forms posted to this method should contain a hidden field:
187 # Forms posted to this method should contain a hidden field:
167 # <input type="hidden" name="_method" value="DELETE" />
188 # <input type="hidden" name="_method" value="DELETE" />
168 # Or using helpers:
189 # Or using helpers:
169 # h.form(url('repos_group', id=ID),
190 # h.form(url('repos_group', id=ID),
170 # method='delete')
191 # method='delete')
171 # url('repos_group', id=ID)
192 # url('repos_group', id=ID)
172
193
173 gr = RepoGroup.get(id)
194 gr = RepoGroup.get(id)
174 repos = gr.repositories.all()
195 repos = gr.repositories.all()
175 if repos:
196 if repos:
176 h.flash(_('This group contains %s repositores and cannot be '
197 h.flash(_('This group contains %s repositores and cannot be '
177 'deleted' % len(repos)),
198 'deleted' % len(repos)),
178 category='error')
199 category='error')
179 return redirect(url('repos_groups'))
200 return redirect(url('repos_groups'))
180
201
181 try:
202 try:
182 ReposGroupModel().delete(id)
203 ReposGroupModel().delete(id)
183 Session.commit()
204 Session.commit()
184 h.flash(_('removed repos group %s' % gr.group_name), category='success')
205 h.flash(_('removed repos group %s' % gr.group_name), category='success')
185 #TODO: in future action_logger(, '', '', '', self.sa)
206 #TODO: in future action_logger(, '', '', '', self.sa)
186 except IntegrityError, e:
207 except IntegrityError, e:
187 if e.message.find('groups_group_parent_id_fkey') != -1:
208 if e.message.find('groups_group_parent_id_fkey') != -1:
188 log.error(traceback.format_exc())
209 log.error(traceback.format_exc())
189 h.flash(_('Cannot delete this group it still contains '
210 h.flash(_('Cannot delete this group it still contains '
190 'subgroups'),
211 'subgroups'),
191 category='warning')
212 category='warning')
192 else:
213 else:
193 log.error(traceback.format_exc())
214 log.error(traceback.format_exc())
194 h.flash(_('error occurred during deletion of repos '
215 h.flash(_('error occurred during deletion of repos '
195 'group %s' % gr.group_name), category='error')
216 'group %s' % gr.group_name), category='error')
196
217
197 except Exception:
218 except Exception:
198 log.error(traceback.format_exc())
219 log.error(traceback.format_exc())
199 h.flash(_('error occurred during deletion of repos '
220 h.flash(_('error occurred during deletion of repos '
200 'group %s' % gr.group_name), category='error')
221 'group %s' % gr.group_name), category='error')
201
222
202 return redirect(url('repos_groups'))
223 return redirect(url('repos_groups'))
203
224
225 @HasReposGroupPermissionAnyDecorator('group.admin')
226 def delete_repos_group_user_perm(self, group_name):
227 """
228 DELETE an existing repositories group permission user
229
230 :param group_name:
231 """
232
233 try:
234 ReposGroupModel().revoke_user_permission(
235 repos_group=group_name, user=request.POST['user_id']
236 )
237 Session.commit()
238 except Exception:
239 log.error(traceback.format_exc())
240 h.flash(_('An error occurred during deletion of group user'),
241 category='error')
242 raise HTTPInternalServerError()
243
244 @HasReposGroupPermissionAnyDecorator('group.admin')
245 def delete_repos_group_users_group_perm(self, group_name):
246 """
247 DELETE an existing repositories group permission users group
248
249 :param group_name:
250 """
251
252 try:
253 ReposGroupModel().revoke_users_group_permission(
254 repos_group=group_name,
255 group_name=request.POST['users_group_id']
256 )
257 Session.commit()
258 except Exception:
259 log.error(traceback.format_exc())
260 h.flash(_('An error occurred during deletion of group'
261 ' users groups'),
262 category='error')
263 raise HTTPInternalServerError()
264
204 def show_by_name(self, group_name):
265 def show_by_name(self, group_name):
205 id_ = RepoGroup.get_by_group_name(group_name).group_id
266 id_ = RepoGroup.get_by_group_name(group_name).group_id
206 return self.show(id_)
267 return self.show(id_)
207
268
269 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
270 'group.admin')
208 def show(self, id, format='html'):
271 def show(self, id, format='html'):
209 """GET /repos_groups/id: Show a specific item"""
272 """GET /repos_groups/id: Show a specific item"""
210 # url('repos_group', id=ID)
273 # url('repos_group', id=ID)
211
274
212 c.group = RepoGroup.get(id)
275 c.group = RepoGroup.get(id)
213
276
214 if c.group:
277 if c.group:
215 c.group_repos = c.group.repositories.all()
278 c.group_repos = c.group.repositories.all()
216 else:
279 else:
217 return redirect(url('home'))
280 return redirect(url('home'))
218
281
219 #overwrite our cached list with current filter
282 #overwrite our cached list with current filter
220 gr_filter = c.group_repos
283 gr_filter = c.group_repos
221 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
284 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
222
285
223 c.repos_list = c.cached_repo_list
286 c.repos_list = c.cached_repo_list
224
287
225 c.repo_cnt = 0
288 c.repo_cnt = 0
226
289
227 c.groups = self.sa.query(RepoGroup).order_by(RepoGroup.group_name)\
290 c.groups = self.sa.query(RepoGroup).order_by(RepoGroup.group_name)\
228 .filter(RepoGroup.group_parent_id == id).all()
291 .filter(RepoGroup.group_parent_id == id).all()
229
292
230 return render('admin/repos_groups/repos_groups.html')
293 return render('admin/repos_groups/repos_groups.html')
231
294
232 @HasPermissionAnyDecorator('hg.admin')
295 @HasPermissionAnyDecorator('hg.admin')
233 def edit(self, id, format='html'):
296 def edit(self, id, format='html'):
234 """GET /repos_groups/id/edit: Form to edit an existing item"""
297 """GET /repos_groups/id/edit: Form to edit an existing item"""
235 # url('edit_repos_group', id=ID)
298 # url('edit_repos_group', id=ID)
236
299
237 id_ = int(id)
300 id_ = int(id)
238
301
239 c.repos_group = RepoGroup.get(id_)
302 c.repos_group = RepoGroup.get(id_)
240 defaults = self.__load_data(id_)
303 defaults = self.__load_data(id_)
241
304
242 # we need to exclude this group from the group list for editing
305 # we need to exclude this group from the group list for editing
243 c.repo_groups = filter(lambda x:x[0] != id_, c.repo_groups)
306 c.repo_groups = filter(lambda x: x[0] != id_, c.repo_groups)
244
307
245 return htmlfill.render(
308 return htmlfill.render(
246 render('admin/repos_groups/repos_groups_edit.html'),
309 render('admin/repos_groups/repos_groups_edit.html'),
247 defaults=defaults,
310 defaults=defaults,
248 encoding="UTF-8",
311 encoding="UTF-8",
249 force_defaults=False
312 force_defaults=False
250 )
313 )
@@ -1,510 +1,571 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.api
3 rhodecode.controllers.api
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 API controller for RhodeCode
6 API controller for RhodeCode
7
7
8 :created_on: Aug 20, 2011
8 :created_on: Aug 20, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import traceback
28 import traceback
29 import logging
29 import logging
30
30
31 from sqlalchemy.orm.exc import NoResultFound
31 from sqlalchemy.orm.exc import NoResultFound
32
32
33 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
33 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
34 from rhodecode.lib.auth import HasPermissionAllDecorator, \
34 from rhodecode.lib.auth import HasPermissionAllDecorator, \
35 HasPermissionAnyDecorator
35 HasPermissionAnyDecorator
36
36
37 from rhodecode.model.meta import Session
37 from rhodecode.model.meta import Session
38 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.user import UserModel
41 from rhodecode.model.user import UserModel
42 from rhodecode.model.repo_permission import RepositoryPermissionModel
42 from rhodecode.model.repo_permission import RepositoryPermissionModel
43 from rhodecode.model.users_group import UsersGroupModel
43 from rhodecode.model.users_group import UsersGroupModel
44 from rhodecode.model.repos_group import ReposGroupModel
44 from rhodecode.model.repos_group import ReposGroupModel
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class ApiController(JSONRPCController):
50 class ApiController(JSONRPCController):
51 """
51 """
52 API Controller
52 API Controller
53
53
54
54
55 Each method needs to have USER as argument this is then based on given
55 Each method needs to have USER as argument this is then based on given
56 API_KEY propagated as instance of user object
56 API_KEY propagated as instance of user object
57
57
58 Preferably this should be first argument also
58 Preferably this should be first argument also
59
59
60
60
61 Each function should also **raise** JSONRPCError for any
61 Each function should also **raise** JSONRPCError for any
62 errors that happens
62 errors that happens
63
63
64 """
64 """
65
65
66 @HasPermissionAllDecorator('hg.admin')
66 @HasPermissionAllDecorator('hg.admin')
67 def pull(self, apiuser, repo_name):
67 def pull(self, apiuser, repo_name):
68 """
68 """
69 Dispatch pull action on given repo
69 Dispatch pull action on given repo
70
70
71
71
72 :param user:
72 :param user:
73 :param repo_name:
73 :param repo_name:
74 """
74 """
75
75
76 if Repository.is_valid(repo_name) is False:
76 if Repository.is_valid(repo_name) is False:
77 raise JSONRPCError('Unknown repo "%s"' % repo_name)
77 raise JSONRPCError('Unknown repo "%s"' % repo_name)
78
78
79 try:
79 try:
80 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
80 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
81 return 'Pulled from %s' % repo_name
81 return 'Pulled from %s' % repo_name
82 except Exception:
82 except Exception:
83 raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
83 raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
84
84
85 @HasPermissionAllDecorator('hg.admin')
85 @HasPermissionAllDecorator('hg.admin')
86 def get_user(self, apiuser, username):
86 def get_user(self, apiuser, username):
87 """"
87 """"
88 Get a user by username
88 Get a user by username
89
89
90 :param apiuser:
90 :param apiuser:
91 :param username:
91 :param username:
92 """
92 """
93
93
94 user = User.get_by_username(username)
94 user = User.get_by_username(username)
95 if not user:
95 if not user:
96 return None
96 return None
97
97
98 return dict(
98 return dict(
99 id=user.user_id,
99 id=user.user_id,
100 username=user.username,
100 username=user.username,
101 firstname=user.name,
101 firstname=user.name,
102 lastname=user.lastname,
102 lastname=user.lastname,
103 email=user.email,
103 email=user.email,
104 active=user.active,
104 active=user.active,
105 admin=user.admin,
105 admin=user.admin,
106 ldap=user.ldap_dn
106 ldap=user.ldap_dn
107 )
107 )
108
108
109 @HasPermissionAllDecorator('hg.admin')
109 @HasPermissionAllDecorator('hg.admin')
110 def get_users(self, apiuser):
110 def get_users(self, apiuser):
111 """"
111 """"
112 Get all users
112 Get all users
113
113
114 :param apiuser:
114 :param apiuser:
115 """
115 """
116
116
117 result = []
117 result = []
118 for user in User.getAll():
118 for user in User.getAll():
119 result.append(
119 result.append(
120 dict(
120 dict(
121 id=user.user_id,
121 id=user.user_id,
122 username=user.username,
122 username=user.username,
123 firstname=user.name,
123 firstname=user.name,
124 lastname=user.lastname,
124 lastname=user.lastname,
125 email=user.email,
125 email=user.email,
126 active=user.active,
126 active=user.active,
127 admin=user.admin,
127 admin=user.admin,
128 ldap=user.ldap_dn
128 ldap=user.ldap_dn
129 )
129 )
130 )
130 )
131 return result
131 return result
132
132
133 @HasPermissionAllDecorator('hg.admin')
133 @HasPermissionAllDecorator('hg.admin')
134 def create_user(self, apiuser, username, password, email, firstname=None,
134 def create_user(self, apiuser, username, password, email, firstname=None,
135 lastname=None, active=True, admin=False, ldap_dn=None):
135 lastname=None, active=True, admin=False, ldap_dn=None):
136 """
136 """
137 Create new user or updates current one
137 Create new user or updates current one
138
138
139 :param apiuser:
139 :param apiuser:
140 :param username:
140 :param username:
141 :param password:
141 :param password:
142 :param email:
142 :param email:
143 :param name:
143 :param name:
144 :param lastname:
144 :param lastname:
145 :param active:
145 :param active:
146 :param admin:
146 :param admin:
147 :param ldap_dn:
147 :param ldap_dn:
148 """
148 """
149
149
150 if User.get_by_username(username):
150 if User.get_by_username(username):
151 raise JSONRPCError("user %s already exist" % username)
151 raise JSONRPCError("user %s already exist" % username)
152
152
153 try:
153 try:
154 usr = UserModel().create_or_update(
154 usr = UserModel().create_or_update(
155 username, password, email, firstname,
155 username, password, email, firstname,
156 lastname, active, admin, ldap_dn
156 lastname, active, admin, ldap_dn
157 )
157 )
158 Session.commit()
158 Session.commit()
159 return dict(
159 return dict(
160 id=usr.user_id,
160 id=usr.user_id,
161 msg='created new user %s' % username
161 msg='created new user %s' % username
162 )
162 )
163 except Exception:
163 except Exception:
164 log.error(traceback.format_exc())
164 log.error(traceback.format_exc())
165 raise JSONRPCError('failed to create user %s' % username)
165 raise JSONRPCError('failed to create user %s' % username)
166
166
167 @HasPermissionAllDecorator('hg.admin')
167 @HasPermissionAllDecorator('hg.admin')
168 def get_users_group(self, apiuser, group_name):
168 def get_users_group(self, apiuser, group_name):
169 """"
169 """"
170 Get users group by name
170 Get users group by name
171
171
172 :param apiuser:
172 :param apiuser:
173 :param group_name:
173 :param group_name:
174 """
174 """
175
175
176 users_group = UsersGroup.get_by_group_name(group_name)
176 users_group = UsersGroup.get_by_group_name(group_name)
177 if not users_group:
177 if not users_group:
178 return None
178 return None
179
179
180 members = []
180 members = []
181 for user in users_group.members:
181 for user in users_group.members:
182 user = user.user
182 user = user.user
183 members.append(dict(id=user.user_id,
183 members.append(dict(id=user.user_id,
184 username=user.username,
184 username=user.username,
185 firstname=user.name,
185 firstname=user.name,
186 lastname=user.lastname,
186 lastname=user.lastname,
187 email=user.email,
187 email=user.email,
188 active=user.active,
188 active=user.active,
189 admin=user.admin,
189 admin=user.admin,
190 ldap=user.ldap_dn))
190 ldap=user.ldap_dn))
191
191
192 return dict(id=users_group.users_group_id,
192 return dict(id=users_group.users_group_id,
193 group_name=users_group.users_group_name,
193 group_name=users_group.users_group_name,
194 active=users_group.users_group_active,
194 active=users_group.users_group_active,
195 members=members)
195 members=members)
196
196
197 @HasPermissionAllDecorator('hg.admin')
197 @HasPermissionAllDecorator('hg.admin')
198 def get_users_groups(self, apiuser):
198 def get_users_groups(self, apiuser):
199 """"
199 """"
200 Get all users groups
200 Get all users groups
201
201
202 :param apiuser:
202 :param apiuser:
203 """
203 """
204
204
205 result = []
205 result = []
206 for users_group in UsersGroup.getAll():
206 for users_group in UsersGroup.getAll():
207 members = []
207 members = []
208 for user in users_group.members:
208 for user in users_group.members:
209 user = user.user
209 user = user.user
210 members.append(dict(id=user.user_id,
210 members.append(dict(id=user.user_id,
211 username=user.username,
211 username=user.username,
212 firstname=user.name,
212 firstname=user.name,
213 lastname=user.lastname,
213 lastname=user.lastname,
214 email=user.email,
214 email=user.email,
215 active=user.active,
215 active=user.active,
216 admin=user.admin,
216 admin=user.admin,
217 ldap=user.ldap_dn))
217 ldap=user.ldap_dn))
218
218
219 result.append(dict(id=users_group.users_group_id,
219 result.append(dict(id=users_group.users_group_id,
220 group_name=users_group.users_group_name,
220 group_name=users_group.users_group_name,
221 active=users_group.users_group_active,
221 active=users_group.users_group_active,
222 members=members))
222 members=members))
223 return result
223 return result
224
224
225 @HasPermissionAllDecorator('hg.admin')
225 @HasPermissionAllDecorator('hg.admin')
226 def create_users_group(self, apiuser, group_name, active=True):
226 def create_users_group(self, apiuser, group_name, active=True):
227 """
227 """
228 Creates an new usergroup
228 Creates an new usergroup
229
229
230 :param group_name:
230 :param group_name:
231 :param active:
231 :param active:
232 """
232 """
233
233
234 if self.get_users_group(apiuser, group_name):
234 if self.get_users_group(apiuser, group_name):
235 raise JSONRPCError("users group %s already exist" % group_name)
235 raise JSONRPCError("users group %s already exist" % group_name)
236
236
237 try:
237 try:
238 ug = UsersGroupModel().create(name=group_name, active=active)
238 ug = UsersGroupModel().create(name=group_name, active=active)
239 Session.commit()
239 Session.commit()
240 return dict(id=ug.users_group_id,
240 return dict(id=ug.users_group_id,
241 msg='created new users group %s' % group_name)
241 msg='created new users group %s' % group_name)
242 except Exception:
242 except Exception:
243 log.error(traceback.format_exc())
243 log.error(traceback.format_exc())
244 raise JSONRPCError('failed to create group %s' % group_name)
244 raise JSONRPCError('failed to create group %s' % group_name)
245
245
246 @HasPermissionAllDecorator('hg.admin')
246 @HasPermissionAllDecorator('hg.admin')
247 def add_user_to_users_group(self, apiuser, group_name, username):
247 def add_user_to_users_group(self, apiuser, group_name, username):
248 """"
248 """"
249 Add a user to a group
249 Add a user to a group
250
250
251 :param apiuser:
251 :param apiuser:
252 :param group_name:
252 :param group_name:
253 :param username:
253 :param username:
254 """
254 """
255
255
256 try:
256 try:
257 users_group = UsersGroup.get_by_group_name(group_name)
257 users_group = UsersGroup.get_by_group_name(group_name)
258 if not users_group:
258 if not users_group:
259 raise JSONRPCError('unknown users group %s' % group_name)
259 raise JSONRPCError('unknown users group %s' % group_name)
260
260
261 try:
261 try:
262 user = User.get_by_username(username)
262 user = User.get_by_username(username)
263 except NoResultFound:
263 except NoResultFound:
264 raise JSONRPCError('unknown user %s' % username)
264 raise JSONRPCError('unknown user %s' % username)
265
265
266 ugm = UsersGroupModel().add_user_to_group(users_group, user)
266 ugm = UsersGroupModel().add_user_to_group(users_group, user)
267 Session.commit()
267 Session.commit()
268 return dict(id=ugm.users_group_member_id,
268 return dict(id=ugm.users_group_member_id,
269 msg='created new users group member')
269 msg='created new users group member')
270 except Exception:
270 except Exception:
271 log.error(traceback.format_exc())
271 log.error(traceback.format_exc())
272 raise JSONRPCError('failed to create users group member')
272 raise JSONRPCError('failed to create users group member')
273
273
274 @HasPermissionAnyDecorator('hg.admin')
274 @HasPermissionAnyDecorator('hg.admin')
275 def get_repo(self, apiuser, repo_name):
275 def get_repo(self, apiuser, repo_name):
276 """"
276 """"
277 Get repository by name
277 Get repository by name
278
278
279 :param apiuser:
279 :param apiuser:
280 :param repo_name:
280 :param repo_name:
281 """
281 """
282
282
283 repo = Repository.get_by_repo_name(repo_name)
283 repo = Repository.get_by_repo_name(repo_name)
284 if repo is None:
284 if repo is None:
285 raise JSONRPCError('unknown repository %s' % repo)
285 raise JSONRPCError('unknown repository %s' % repo)
286
286
287 members = []
287 members = []
288 for user in repo.repo_to_perm:
288 for user in repo.repo_to_perm:
289 perm = user.permission.permission_name
289 perm = user.permission.permission_name
290 user = user.user
290 user = user.user
291 members.append(
291 members.append(
292 dict(
292 dict(
293 type_="user",
293 type_="user",
294 id=user.user_id,
294 id=user.user_id,
295 username=user.username,
295 username=user.username,
296 firstname=user.name,
296 firstname=user.name,
297 lastname=user.lastname,
297 lastname=user.lastname,
298 email=user.email,
298 email=user.email,
299 active=user.active,
299 active=user.active,
300 admin=user.admin,
300 admin=user.admin,
301 ldap=user.ldap_dn,
301 ldap=user.ldap_dn,
302 permission=perm
302 permission=perm
303 )
303 )
304 )
304 )
305 for users_group in repo.users_group_to_perm:
305 for users_group in repo.users_group_to_perm:
306 perm = users_group.permission.permission_name
306 perm = users_group.permission.permission_name
307 users_group = users_group.users_group
307 users_group = users_group.users_group
308 members.append(
308 members.append(
309 dict(
309 dict(
310 type_="users_group",
310 type_="users_group",
311 id=users_group.users_group_id,
311 id=users_group.users_group_id,
312 name=users_group.users_group_name,
312 name=users_group.users_group_name,
313 active=users_group.users_group_active,
313 active=users_group.users_group_active,
314 permission=perm
314 permission=perm
315 )
315 )
316 )
316 )
317
317
318 return dict(
318 return dict(
319 id=repo.repo_id,
319 id=repo.repo_id,
320 repo_name=repo.repo_name,
320 repo_name=repo.repo_name,
321 type=repo.repo_type,
321 type=repo.repo_type,
322 description=repo.description,
322 description=repo.description,
323 members=members
323 members=members
324 )
324 )
325
325
326 @HasPermissionAnyDecorator('hg.admin')
326 @HasPermissionAnyDecorator('hg.admin')
327 def get_repos(self, apiuser):
327 def get_repos(self, apiuser):
328 """"
328 """"
329 Get all repositories
329 Get all repositories
330
330
331 :param apiuser:
331 :param apiuser:
332 """
332 """
333
333
334 result = []
334 result = []
335 for repository in Repository.getAll():
335 for repository in Repository.getAll():
336 result.append(
336 result.append(
337 dict(
337 dict(
338 id=repository.repo_id,
338 id=repository.repo_id,
339 repo_name=repository.repo_name,
339 repo_name=repository.repo_name,
340 type=repository.repo_type,
340 type=repository.repo_type,
341 description=repository.description
341 description=repository.description
342 )
342 )
343 )
343 )
344 return result
344 return result
345
345
346 @HasPermissionAnyDecorator('hg.admin')
346 @HasPermissionAnyDecorator('hg.admin')
347 def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
347 def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
348 ret_type='all'):
348 ret_type='all'):
349 """
349 """
350 returns a list of nodes and it's children
350 returns a list of nodes and it's children
351 for a given path at given revision. It's possible to specify ret_type
351 for a given path at given revision. It's possible to specify ret_type
352 to show only files or dirs
352 to show only files or dirs
353
353
354 :param apiuser:
354 :param apiuser:
355 :param repo_name: name of repository
355 :param repo_name: name of repository
356 :param revision: revision for which listing should be done
356 :param revision: revision for which listing should be done
357 :param root_path: path from which start displaying
357 :param root_path: path from which start displaying
358 :param ret_type: return type 'all|files|dirs' nodes
358 :param ret_type: return type 'all|files|dirs' nodes
359 """
359 """
360 try:
360 try:
361 _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
361 _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
362 flat=False)
362 flat=False)
363 _map = {
363 _map = {
364 'all': _d + _f,
364 'all': _d + _f,
365 'files': _f,
365 'files': _f,
366 'dirs': _d,
366 'dirs': _d,
367 }
367 }
368 return _map[ret_type]
368 return _map[ret_type]
369 except KeyError:
369 except KeyError:
370 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
370 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
371 except Exception, e:
371 except Exception, e:
372 raise JSONRPCError(e)
372 raise JSONRPCError(e)
373
373
374 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
374 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
375 def create_repo(self, apiuser, repo_name, owner_name, description='',
375 def create_repo(self, apiuser, repo_name, owner_name, description='',
376 repo_type='hg', private=False):
376 repo_type='hg', private=False):
377 """
377 """
378 Create a repository
378 Create a repository
379
379
380 :param apiuser:
380 :param apiuser:
381 :param repo_name:
381 :param repo_name:
382 :param description:
382 :param description:
383 :param type:
383 :param type:
384 :param private:
384 :param private:
385 :param owner_name:
385 :param owner_name:
386 """
386 """
387
387
388 try:
388 try:
389 try:
389 try:
390 owner = User.get_by_username(owner_name)
390 owner = User.get_by_username(owner_name)
391 except NoResultFound:
391 except NoResultFound:
392 raise JSONRPCError('unknown user %s' % owner)
392 raise JSONRPCError('unknown user %s' % owner)
393
393
394 if Repository.get_by_repo_name(repo_name):
394 if Repository.get_by_repo_name(repo_name):
395 raise JSONRPCError("repo %s already exist" % repo_name)
395 raise JSONRPCError("repo %s already exist" % repo_name)
396
396
397 groups = repo_name.split('/')
397 groups = repo_name.split('/')
398 real_name = groups[-1]
398 real_name = groups[-1]
399 groups = groups[:-1]
399 groups = groups[:-1]
400 parent_id = None
400 parent_id = None
401 for g in groups:
401 for g in groups:
402 group = RepoGroup.get_by_group_name(g)
402 group = RepoGroup.get_by_group_name(g)
403 if not group:
403 if not group:
404 group = ReposGroupModel().create(
404 group = ReposGroupModel().create(g, '', parent_id)
405 dict(
406 group_name=g,
407 group_description='',
408 group_parent_id=parent_id
409 )
410 )
411 parent_id = group.group_id
405 parent_id = group.group_id
412
406
413 repo = RepoModel().create(
407 repo = RepoModel().create(
414 dict(
408 dict(
415 repo_name=real_name,
409 repo_name=real_name,
416 repo_name_full=repo_name,
410 repo_name_full=repo_name,
417 description=description,
411 description=description,
418 private=private,
412 private=private,
419 repo_type=repo_type,
413 repo_type=repo_type,
420 repo_group=parent_id,
414 repo_group=parent_id,
421 clone_uri=None
415 clone_uri=None
422 ),
416 ),
423 owner
417 owner
424 )
418 )
425 Session.commit()
419 Session.commit()
426
420
427 return dict(
421 return dict(
428 id=repo.repo_id,
422 id=repo.repo_id,
429 msg="Created new repository %s" % repo.repo_name
423 msg="Created new repository %s" % repo.repo_name
430 )
424 )
431
425
432 except Exception:
426 except Exception:
433 log.error(traceback.format_exc())
427 log.error(traceback.format_exc())
434 raise JSONRPCError('failed to create repository %s' % repo_name)
428 raise JSONRPCError('failed to create repository %s' % repo_name)
435
429
436 @HasPermissionAnyDecorator('hg.admin')
430 @HasPermissionAnyDecorator('hg.admin')
437 def add_user_to_repo(self, apiuser, repo_name, username, perm):
431 def grant_user_permission(self, repo_name, username, perm):
438 """
432 """
439 Add permission for a user to a repository
433 Grant permission for user on given repository, or update existing one
434 if found
440
435
441 :param apiuser:
442 :param repo_name:
436 :param repo_name:
443 :param username:
437 :param username:
444 :param perm:
438 :param perm:
445 """
439 """
446
440
447 try:
441 try:
448 repo = Repository.get_by_repo_name(repo_name)
442 repo = Repository.get_by_repo_name(repo_name)
449 if repo is None:
443 if repo is None:
450 raise JSONRPCError('unknown repository %s' % repo)
444 raise JSONRPCError('unknown repository %s' % repo)
451
445
452 try:
446 user = User.get_by_username(username)
453 user = User.get_by_username(username)
447 if user is None:
454 except NoResultFound:
448 raise JSONRPCError('unknown user %s' % username)
455 raise JSONRPCError('unknown user %s' % user)
456
449
457 RepositoryPermissionModel()\
450 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
458 .update_or_delete_user_permission(repo, user, perm)
451
459 Session.commit()
452 Session.commit()
460
461 return dict(
453 return dict(
462 msg='Added perm: %s for %s in repo: %s' % (
454 msg='Granted perm: %s for user: %s in repo: %s' % (
463 perm, username, repo_name
455 perm, username, repo_name
464 )
456 )
465 )
457 )
466 except Exception:
458 except Exception:
467 log.error(traceback.format_exc())
459 log.error(traceback.format_exc())
468 raise JSONRPCError(
460 raise JSONRPCError(
469 'failed to edit permission %(repo)s for %(user)s' % dict(
461 'failed to edit permission %(repo)s for %(user)s' % dict(
470 user=username, repo=repo_name
462 user=username, repo=repo_name
471 )
463 )
472 )
464 )
473
465
474 @HasPermissionAnyDecorator('hg.admin')
466 @HasPermissionAnyDecorator('hg.admin')
475 def add_users_group_to_repo(self, apiuser, repo_name, group_name, perm):
467 def revoke_user_permission(self, repo_name, username):
468 """
469 Revoke permission for user on given repository
470
471 :param repo_name:
472 :param username:
476 """
473 """
477 Add permission for a users group to a repository
474
475 try:
476 repo = Repository.get_by_repo_name(repo_name)
477 if repo is None:
478 raise JSONRPCError('unknown repository %s' % repo)
479
480 user = User.get_by_username(username)
481 if user is None:
482 raise JSONRPCError('unknown user %s' % username)
483
484 RepoModel().revoke_user_permission(repo=repo_name, user=username)
478
485
479 :param apiuser:
486 Session.commit()
487 return dict(
488 msg='Revoked perm for user: %s in repo: %s' % (
489 username, repo_name
490 )
491 )
492 except Exception:
493 log.error(traceback.format_exc())
494 raise JSONRPCError(
495 'failed to edit permission %(repo)s for %(user)s' % dict(
496 user=username, repo=repo_name
497 )
498 )
499
500 @HasPermissionAnyDecorator('hg.admin')
501 def grant_users_group_permission(self, repo_name, group_name, perm):
502 """
503 Grant permission for users group on given repository, or update
504 existing one if found
505
480 :param repo_name:
506 :param repo_name:
481 :param group_name:
507 :param group_name:
482 :param perm:
508 :param perm:
483 """
509 """
484
510
485 try:
511 try:
486 repo = Repository.get_by_repo_name(repo_name)
512 repo = Repository.get_by_repo_name(repo_name)
487 if repo is None:
513 if repo is None:
488 raise JSONRPCError('unknown repository %s' % repo)
514 raise JSONRPCError('unknown repository %s' % repo)
489
515
490 try:
516 user_group = UsersGroup.get_by_group_name(group_name)
491 user_group = UsersGroup.get_by_group_name(group_name)
517 if user_group is None:
492 except NoResultFound:
493 raise JSONRPCError('unknown users group %s' % user_group)
518 raise JSONRPCError('unknown users group %s' % user_group)
494
519
495 RepositoryPermissionModel()\
520 RepoModel().grant_users_group_permission(repo=repo_name,
496 .update_or_delete_users_group_permission(repo, user_group,
521 group_name=group_name,
497 perm)
522 perm=perm)
523
498 Session.commit()
524 Session.commit()
499 return dict(
525 return dict(
500 msg='Added perm: %s for %s in repo: %s' % (
526 msg='Granted perm: %s for group: %s in repo: %s' % (
501 perm, group_name, repo_name
527 perm, group_name, repo_name
502 )
528 )
503 )
529 )
504 except Exception:
530 except Exception:
505 log.error(traceback.format_exc())
531 log.error(traceback.format_exc())
506 raise JSONRPCError(
532 raise JSONRPCError(
507 'failed to edit permission %(repo)s for %(usergr)s' % dict(
533 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
508 usergr=group_name, repo=repo_name
534 usersgr=group_name, repo=repo_name
509 )
535 )
510 )
536 )
537
538 @HasPermissionAnyDecorator('hg.admin')
539 def revoke_users_group_permission(self, repo_name, group_name):
540 """
541 Revoke permission for users group on given repository
542
543 :param repo_name:
544 :param group_name:
545 """
546
547 try:
548 repo = Repository.get_by_repo_name(repo_name)
549 if repo is None:
550 raise JSONRPCError('unknown repository %s' % repo)
551
552 user_group = UsersGroup.get_by_group_name(group_name)
553 if user_group is None:
554 raise JSONRPCError('unknown users group %s' % user_group)
555
556 RepoModel().revoke_users_group_permission(repo=repo_name,
557 group_name=group_name)
558
559 Session.commit()
560 return dict(
561 msg='Revoked perm for group: %s in repo: %s' % (
562 group_name, repo_name
563 )
564 )
565 except Exception:
566 log.error(traceback.format_exc())
567 raise JSONRPCError(
568 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
569 usersgr=group_name, repo=repo_name
570 )
571 )
@@ -1,68 +1,65 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.home
3 rhodecode.controllers.home
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Home controller for Rhodecode
6 Home controller for Rhodecode
7
7
8 :created_on: Feb 18, 2010
8 :created_on: Feb 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27
27
28 from pylons import tmpl_context as c, request
28 from pylons import tmpl_context as c, request
29 from paste.httpexceptions import HTTPBadRequest
29 from paste.httpexceptions import HTTPBadRequest
30
30
31 from rhodecode.lib.auth import LoginRequired
31 from rhodecode.lib.auth import LoginRequired
32 from rhodecode.lib.base import BaseController, render
32 from rhodecode.lib.base import BaseController, render
33 from rhodecode.model.db import RepoGroup, Repository
33 from rhodecode.model.db import Repository
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class HomeController(BaseController):
38 class HomeController(BaseController):
39
39
40 @LoginRequired()
40 @LoginRequired()
41 def __before__(self):
41 def __before__(self):
42 super(HomeController, self).__before__()
42 super(HomeController, self).__before__()
43
43
44 def index(self):
44 def index(self):
45
46 c.repos_list = self.scm_model.get_repos()
45 c.repos_list = self.scm_model.get_repos()
47
46 c.groups = self.scm_model.get_repos_groups()
48 c.groups = RepoGroup.query()\
49 .filter(RepoGroup.group_parent_id == None).all()
50
47
51 return render('/index.html')
48 return render('/index.html')
52
49
53 def repo_switcher(self):
50 def repo_switcher(self):
54 if request.is_xhr:
51 if request.is_xhr:
55 all_repos = Repository.query().order_by(Repository.repo_name).all()
52 all_repos = Repository.query().order_by(Repository.repo_name).all()
56 c.repos_list = self.scm_model.get_repos(all_repos,
53 c.repos_list = self.scm_model.get_repos(all_repos,
57 sort_key='name_sort')
54 sort_key='name_sort')
58 return render('/repo_switcher_list.html')
55 return render('/repo_switcher_list.html')
59 else:
56 else:
60 return HTTPBadRequest()
57 return HTTPBadRequest()
61
58
62 def branch_tag_switcher(self, repo_name):
59 def branch_tag_switcher(self, repo_name):
63 if request.is_xhr:
60 if request.is_xhr:
64 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
61 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
65 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
62 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
66 return render('/switch_to_list.html')
63 return render('/switch_to_list.html')
67 else:
64 else:
68 return HTTPBadRequest()
65 return HTTPBadRequest()
@@ -1,450 +1,454 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.__init__
3 rhodecode.lib.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import re
27 import re
28 from vcs.utils.lazy import LazyProperty
29
28
30
29 def __get_lem():
31 def __get_lem():
30 from pygments import lexers
32 from pygments import lexers
31 from string import lower
33 from string import lower
32 from collections import defaultdict
34 from collections import defaultdict
33
35
34 d = defaultdict(lambda: [])
36 d = defaultdict(lambda: [])
35
37
36 def __clean(s):
38 def __clean(s):
37 s = s.lstrip('*')
39 s = s.lstrip('*')
38 s = s.lstrip('.')
40 s = s.lstrip('.')
39
41
40 if s.find('[') != -1:
42 if s.find('[') != -1:
41 exts = []
43 exts = []
42 start, stop = s.find('['), s.find(']')
44 start, stop = s.find('['), s.find(']')
43
45
44 for suffix in s[start + 1:stop]:
46 for suffix in s[start + 1:stop]:
45 exts.append(s[:s.find('[')] + suffix)
47 exts.append(s[:s.find('[')] + suffix)
46 return map(lower, exts)
48 return map(lower, exts)
47 else:
49 else:
48 return map(lower, [s])
50 return map(lower, [s])
49
51
50 for lx, t in sorted(lexers.LEXERS.items()):
52 for lx, t in sorted(lexers.LEXERS.items()):
51 m = map(__clean, t[-2])
53 m = map(__clean, t[-2])
52 if m:
54 if m:
53 m = reduce(lambda x, y: x + y, m)
55 m = reduce(lambda x, y: x + y, m)
54 for ext in m:
56 for ext in m:
55 desc = lx.replace('Lexer', '')
57 desc = lx.replace('Lexer', '')
56 d[ext].append(desc)
58 d[ext].append(desc)
57
59
58 return dict(d)
60 return dict(d)
59
61
60 # language map is also used by whoosh indexer, which for those specified
62 # language map is also used by whoosh indexer, which for those specified
61 # extensions will index it's content
63 # extensions will index it's content
62 LANGUAGES_EXTENSIONS_MAP = __get_lem()
64 LANGUAGES_EXTENSIONS_MAP = __get_lem()
63
65
64 # Additional mappings that are not present in the pygments lexers
66 # Additional mappings that are not present in the pygments lexers
65 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
67 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
66 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
68 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
67
69
68 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
70 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
69
71
70 # list of readme files to search in file tree and display in summary
72 # list of readme files to search in file tree and display in summary
71 # attached weights defines the search order lower is first
73 # attached weights defines the search order lower is first
72 ALL_READMES = [
74 ALL_READMES = [
73 ('readme', 0), ('README', 0), ('Readme', 0),
75 ('readme', 0), ('README', 0), ('Readme', 0),
74 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
76 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
75 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
77 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
76 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
78 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
77 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
79 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
78 ]
80 ]
79
81
80 # extension together with weights to search lower is first
82 # extension together with weights to search lower is first
81 RST_EXTS = [
83 RST_EXTS = [
82 ('', 0), ('.rst', 1), ('.rest', 1),
84 ('', 0), ('.rst', 1), ('.rest', 1),
83 ('.RST', 2) , ('.REST', 2),
85 ('.RST', 2) , ('.REST', 2),
84 ('.txt', 3), ('.TXT', 3)
86 ('.txt', 3), ('.TXT', 3)
85 ]
87 ]
86
88
87 MARKDOWN_EXTS = [
89 MARKDOWN_EXTS = [
88 ('.md', 1), ('.MD', 1),
90 ('.md', 1), ('.MD', 1),
89 ('.mkdn', 2), ('.MKDN', 2),
91 ('.mkdn', 2), ('.MKDN', 2),
90 ('.mdown', 3), ('.MDOWN', 3),
92 ('.mdown', 3), ('.MDOWN', 3),
91 ('.markdown', 4), ('.MARKDOWN', 4)
93 ('.markdown', 4), ('.MARKDOWN', 4)
92 ]
94 ]
93
95
94 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
96 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
95
97
96 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
98 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
97
99
98
100
99 def str2bool(_str):
101 def str2bool(_str):
100 """
102 """
101 returs True/False value from given string, it tries to translate the
103 returs True/False value from given string, it tries to translate the
102 string into boolean
104 string into boolean
103
105
104 :param _str: string value to translate into boolean
106 :param _str: string value to translate into boolean
105 :rtype: boolean
107 :rtype: boolean
106 :returns: boolean from given string
108 :returns: boolean from given string
107 """
109 """
108 if _str is None:
110 if _str is None:
109 return False
111 return False
110 if _str in (True, False):
112 if _str in (True, False):
111 return _str
113 return _str
112 _str = str(_str).strip().lower()
114 _str = str(_str).strip().lower()
113 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
115 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
114
116
115
117
116 def convert_line_endings(line, mode):
118 def convert_line_endings(line, mode):
117 """
119 """
118 Converts a given line "line end" accordingly to given mode
120 Converts a given line "line end" accordingly to given mode
119
121
120 Available modes are::
122 Available modes are::
121 0 - Unix
123 0 - Unix
122 1 - Mac
124 1 - Mac
123 2 - DOS
125 2 - DOS
124
126
125 :param line: given line to convert
127 :param line: given line to convert
126 :param mode: mode to convert to
128 :param mode: mode to convert to
127 :rtype: str
129 :rtype: str
128 :return: converted line according to mode
130 :return: converted line according to mode
129 """
131 """
130 from string import replace
132 from string import replace
131
133
132 if mode == 0:
134 if mode == 0:
133 line = replace(line, '\r\n', '\n')
135 line = replace(line, '\r\n', '\n')
134 line = replace(line, '\r', '\n')
136 line = replace(line, '\r', '\n')
135 elif mode == 1:
137 elif mode == 1:
136 line = replace(line, '\r\n', '\r')
138 line = replace(line, '\r\n', '\r')
137 line = replace(line, '\n', '\r')
139 line = replace(line, '\n', '\r')
138 elif mode == 2:
140 elif mode == 2:
139 import re
141 import re
140 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
142 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
141 return line
143 return line
142
144
143
145
144 def detect_mode(line, default):
146 def detect_mode(line, default):
145 """
147 """
146 Detects line break for given line, if line break couldn't be found
148 Detects line break for given line, if line break couldn't be found
147 given default value is returned
149 given default value is returned
148
150
149 :param line: str line
151 :param line: str line
150 :param default: default
152 :param default: default
151 :rtype: int
153 :rtype: int
152 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
154 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
153 """
155 """
154 if line.endswith('\r\n'):
156 if line.endswith('\r\n'):
155 return 2
157 return 2
156 elif line.endswith('\n'):
158 elif line.endswith('\n'):
157 return 0
159 return 0
158 elif line.endswith('\r'):
160 elif line.endswith('\r'):
159 return 1
161 return 1
160 else:
162 else:
161 return default
163 return default
162
164
163
165
164 def generate_api_key(username, salt=None):
166 def generate_api_key(username, salt=None):
165 """
167 """
166 Generates unique API key for given username, if salt is not given
168 Generates unique API key for given username, if salt is not given
167 it'll be generated from some random string
169 it'll be generated from some random string
168
170
169 :param username: username as string
171 :param username: username as string
170 :param salt: salt to hash generate KEY
172 :param salt: salt to hash generate KEY
171 :rtype: str
173 :rtype: str
172 :returns: sha1 hash from username+salt
174 :returns: sha1 hash from username+salt
173 """
175 """
174 from tempfile import _RandomNameSequence
176 from tempfile import _RandomNameSequence
175 import hashlib
177 import hashlib
176
178
177 if salt is None:
179 if salt is None:
178 salt = _RandomNameSequence().next()
180 salt = _RandomNameSequence().next()
179
181
180 return hashlib.sha1(username + salt).hexdigest()
182 return hashlib.sha1(username + salt).hexdigest()
181
183
182
184
183 def safe_unicode(str_, from_encoding='utf8'):
185 def safe_unicode(str_, from_encoding='utf8'):
184 """
186 """
185 safe unicode function. Does few trick to turn str_ into unicode
187 safe unicode function. Does few trick to turn str_ into unicode
186
188
187 In case of UnicodeDecode error we try to return it with encoding detected
189 In case of UnicodeDecode error we try to return it with encoding detected
188 by chardet library if it fails fallback to unicode with errors replaced
190 by chardet library if it fails fallback to unicode with errors replaced
189
191
190 :param str_: string to decode
192 :param str_: string to decode
191 :rtype: unicode
193 :rtype: unicode
192 :returns: unicode object
194 :returns: unicode object
193 """
195 """
194 if isinstance(str_, unicode):
196 if isinstance(str_, unicode):
195 return str_
197 return str_
196
198
197 try:
199 try:
198 return unicode(str_)
200 return unicode(str_)
199 except UnicodeDecodeError:
201 except UnicodeDecodeError:
200 pass
202 pass
201
203
202 try:
204 try:
203 return unicode(str_, from_encoding)
205 return unicode(str_, from_encoding)
204 except UnicodeDecodeError:
206 except UnicodeDecodeError:
205 pass
207 pass
206
208
207 try:
209 try:
208 import chardet
210 import chardet
209 encoding = chardet.detect(str_)['encoding']
211 encoding = chardet.detect(str_)['encoding']
210 if encoding is None:
212 if encoding is None:
211 raise Exception()
213 raise Exception()
212 return str_.decode(encoding)
214 return str_.decode(encoding)
213 except (ImportError, UnicodeDecodeError, Exception):
215 except (ImportError, UnicodeDecodeError, Exception):
214 return unicode(str_, from_encoding, 'replace')
216 return unicode(str_, from_encoding, 'replace')
215
217
218
216 def safe_str(unicode_, to_encoding='utf8'):
219 def safe_str(unicode_, to_encoding='utf8'):
217 """
220 """
218 safe str function. Does few trick to turn unicode_ into string
221 safe str function. Does few trick to turn unicode_ into string
219
222
220 In case of UnicodeEncodeError we try to return it with encoding detected
223 In case of UnicodeEncodeError we try to return it with encoding detected
221 by chardet library if it fails fallback to string with errors replaced
224 by chardet library if it fails fallback to string with errors replaced
222
225
223 :param unicode_: unicode to encode
226 :param unicode_: unicode to encode
224 :rtype: str
227 :rtype: str
225 :returns: str object
228 :returns: str object
226 """
229 """
227
230
228 if not isinstance(unicode_, basestring):
231 if not isinstance(unicode_, basestring):
229 return str(unicode_)
232 return str(unicode_)
230
233
231 if isinstance(unicode_, str):
234 if isinstance(unicode_, str):
232 return unicode_
235 return unicode_
233
236
234 try:
237 try:
235 return unicode_.encode(to_encoding)
238 return unicode_.encode(to_encoding)
236 except UnicodeEncodeError:
239 except UnicodeEncodeError:
237 pass
240 pass
238
241
239 try:
242 try:
240 import chardet
243 import chardet
241 encoding = chardet.detect(unicode_)['encoding']
244 encoding = chardet.detect(unicode_)['encoding']
242 print encoding
245 print encoding
243 if encoding is None:
246 if encoding is None:
244 raise UnicodeEncodeError()
247 raise UnicodeEncodeError()
245
248
246 return unicode_.encode(encoding)
249 return unicode_.encode(encoding)
247 except (ImportError, UnicodeEncodeError):
250 except (ImportError, UnicodeEncodeError):
248 return unicode_.encode(to_encoding, 'replace')
251 return unicode_.encode(to_encoding, 'replace')
249
252
250 return safe_str
253 return safe_str
251
254
252
255
253
254 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
256 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
255 """
257 """
256 Custom engine_from_config functions that makes sure we use NullPool for
258 Custom engine_from_config functions that makes sure we use NullPool for
257 file based sqlite databases. This prevents errors on sqlite. This only
259 file based sqlite databases. This prevents errors on sqlite. This only
258 applies to sqlalchemy versions < 0.7.0
260 applies to sqlalchemy versions < 0.7.0
259
261
260 """
262 """
261 import sqlalchemy
263 import sqlalchemy
262 from sqlalchemy import engine_from_config as efc
264 from sqlalchemy import engine_from_config as efc
263 import logging
265 import logging
264
266
265 if int(sqlalchemy.__version__.split('.')[1]) < 7:
267 if int(sqlalchemy.__version__.split('.')[1]) < 7:
266
268
267 # This solution should work for sqlalchemy < 0.7.0, and should use
269 # This solution should work for sqlalchemy < 0.7.0, and should use
268 # proxy=TimerProxy() for execution time profiling
270 # proxy=TimerProxy() for execution time profiling
269
271
270 from sqlalchemy.pool import NullPool
272 from sqlalchemy.pool import NullPool
271 url = configuration[prefix + 'url']
273 url = configuration[prefix + 'url']
272
274
273 if url.startswith('sqlite'):
275 if url.startswith('sqlite'):
274 kwargs.update({'poolclass': NullPool})
276 kwargs.update({'poolclass': NullPool})
275 return efc(configuration, prefix, **kwargs)
277 return efc(configuration, prefix, **kwargs)
276 else:
278 else:
277 import time
279 import time
278 from sqlalchemy import event
280 from sqlalchemy import event
279 from sqlalchemy.engine import Engine
281 from sqlalchemy.engine import Engine
280
282
281 log = logging.getLogger('sqlalchemy.engine')
283 log = logging.getLogger('sqlalchemy.engine')
282 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
284 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
283 engine = efc(configuration, prefix, **kwargs)
285 engine = efc(configuration, prefix, **kwargs)
284
286
285 def color_sql(sql):
287 def color_sql(sql):
286 COLOR_SEQ = "\033[1;%dm"
288 COLOR_SEQ = "\033[1;%dm"
287 COLOR_SQL = YELLOW
289 COLOR_SQL = YELLOW
288 normal = '\x1b[0m'
290 normal = '\x1b[0m'
289 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
291 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
290
292
291 if configuration['debug']:
293 if configuration['debug']:
292 #attach events only for debug configuration
294 #attach events only for debug configuration
293
295
294 def before_cursor_execute(conn, cursor, statement,
296 def before_cursor_execute(conn, cursor, statement,
295 parameters, context, executemany):
297 parameters, context, executemany):
296 context._query_start_time = time.time()
298 context._query_start_time = time.time()
297 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
299 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
298
300
299
301
300 def after_cursor_execute(conn, cursor, statement,
302 def after_cursor_execute(conn, cursor, statement,
301 parameters, context, executemany):
303 parameters, context, executemany):
302 total = time.time() - context._query_start_time
304 total = time.time() - context._query_start_time
303 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
305 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
304
306
305 event.listen(engine, "before_cursor_execute",
307 event.listen(engine, "before_cursor_execute",
306 before_cursor_execute)
308 before_cursor_execute)
307 event.listen(engine, "after_cursor_execute",
309 event.listen(engine, "after_cursor_execute",
308 after_cursor_execute)
310 after_cursor_execute)
309
311
310 return engine
312 return engine
311
313
312
314
313 def age(curdate):
315 def age(curdate):
314 """
316 """
315 turns a datetime into an age string.
317 turns a datetime into an age string.
316
318
317 :param curdate: datetime object
319 :param curdate: datetime object
318 :rtype: unicode
320 :rtype: unicode
319 :returns: unicode words describing age
321 :returns: unicode words describing age
320 """
322 """
321
323
322 from datetime import datetime
324 from datetime import datetime
323 from webhelpers.date import time_ago_in_words
325 from webhelpers.date import time_ago_in_words
324
326
325 _ = lambda s:s
327 _ = lambda s:s
326
328
327 if not curdate:
329 if not curdate:
328 return ''
330 return ''
329
331
330 agescales = [(_(u"year"), 3600 * 24 * 365),
332 agescales = [(_(u"year"), 3600 * 24 * 365),
331 (_(u"month"), 3600 * 24 * 30),
333 (_(u"month"), 3600 * 24 * 30),
332 (_(u"day"), 3600 * 24),
334 (_(u"day"), 3600 * 24),
333 (_(u"hour"), 3600),
335 (_(u"hour"), 3600),
334 (_(u"minute"), 60),
336 (_(u"minute"), 60),
335 (_(u"second"), 1), ]
337 (_(u"second"), 1), ]
336
338
337 age = datetime.now() - curdate
339 age = datetime.now() - curdate
338 age_seconds = (age.days * agescales[2][1]) + age.seconds
340 age_seconds = (age.days * agescales[2][1]) + age.seconds
339 pos = 1
341 pos = 1
340 for scale in agescales:
342 for scale in agescales:
341 if scale[1] <= age_seconds:
343 if scale[1] <= age_seconds:
342 if pos == 6:pos = 5
344 if pos == 6:pos = 5
343 return '%s %s' % (time_ago_in_words(curdate,
345 return '%s %s' % (time_ago_in_words(curdate,
344 agescales[pos][0]), _('ago'))
346 agescales[pos][0]), _('ago'))
345 pos += 1
347 pos += 1
346
348
347 return _(u'just now')
349 return _(u'just now')
348
350
349
351
350 def uri_filter(uri):
352 def uri_filter(uri):
351 """
353 """
352 Removes user:password from given url string
354 Removes user:password from given url string
353
355
354 :param uri:
356 :param uri:
355 :rtype: unicode
357 :rtype: unicode
356 :returns: filtered list of strings
358 :returns: filtered list of strings
357 """
359 """
358 if not uri:
360 if not uri:
359 return ''
361 return ''
360
362
361 proto = ''
363 proto = ''
362
364
363 for pat in ('https://', 'http://'):
365 for pat in ('https://', 'http://'):
364 if uri.startswith(pat):
366 if uri.startswith(pat):
365 uri = uri[len(pat):]
367 uri = uri[len(pat):]
366 proto = pat
368 proto = pat
367 break
369 break
368
370
369 # remove passwords and username
371 # remove passwords and username
370 uri = uri[uri.find('@') + 1:]
372 uri = uri[uri.find('@') + 1:]
371
373
372 # get the port
374 # get the port
373 cred_pos = uri.find(':')
375 cred_pos = uri.find(':')
374 if cred_pos == -1:
376 if cred_pos == -1:
375 host, port = uri, None
377 host, port = uri, None
376 else:
378 else:
377 host, port = uri[:cred_pos], uri[cred_pos + 1:]
379 host, port = uri[:cred_pos], uri[cred_pos + 1:]
378
380
379 return filter(None, [proto, host, port])
381 return filter(None, [proto, host, port])
380
382
381
383
382 def credentials_filter(uri):
384 def credentials_filter(uri):
383 """
385 """
384 Returns a url with removed credentials
386 Returns a url with removed credentials
385
387
386 :param uri:
388 :param uri:
387 """
389 """
388
390
389 uri = uri_filter(uri)
391 uri = uri_filter(uri)
390 #check if we have port
392 #check if we have port
391 if len(uri) > 2 and uri[2]:
393 if len(uri) > 2 and uri[2]:
392 uri[2] = ':' + uri[2]
394 uri[2] = ':' + uri[2]
393
395
394 return ''.join(uri)
396 return ''.join(uri)
395
397
398
396 def get_changeset_safe(repo, rev):
399 def get_changeset_safe(repo, rev):
397 """
400 """
398 Safe version of get_changeset if this changeset doesn't exists for a
401 Safe version of get_changeset if this changeset doesn't exists for a
399 repo it returns a Dummy one instead
402 repo it returns a Dummy one instead
400
403
401 :param repo:
404 :param repo:
402 :param rev:
405 :param rev:
403 """
406 """
404 from vcs.backends.base import BaseRepository
407 from vcs.backends.base import BaseRepository
405 from vcs.exceptions import RepositoryError
408 from vcs.exceptions import RepositoryError
406 if not isinstance(repo, BaseRepository):
409 if not isinstance(repo, BaseRepository):
407 raise Exception('You must pass an Repository '
410 raise Exception('You must pass an Repository '
408 'object as first argument got %s', type(repo))
411 'object as first argument got %s', type(repo))
409
412
410 try:
413 try:
411 cs = repo.get_changeset(rev)
414 cs = repo.get_changeset(rev)
412 except RepositoryError:
415 except RepositoryError:
413 from rhodecode.lib.utils import EmptyChangeset
416 from rhodecode.lib.utils import EmptyChangeset
414 cs = EmptyChangeset(requested_revision=rev)
417 cs = EmptyChangeset(requested_revision=rev)
415 return cs
418 return cs
416
419
417
420
418 def get_current_revision(quiet=False):
421 def get_current_revision(quiet=False):
419 """
422 """
420 Returns tuple of (number, id) from repository containing this package
423 Returns tuple of (number, id) from repository containing this package
421 or None if repository could not be found.
424 or None if repository could not be found.
422
425
423 :param quiet: prints error for fetching revision if True
426 :param quiet: prints error for fetching revision if True
424 """
427 """
425
428
426 try:
429 try:
427 from vcs import get_repo
430 from vcs import get_repo
428 from vcs.utils.helpers import get_scm
431 from vcs.utils.helpers import get_scm
429 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
432 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
430 scm = get_scm(repopath)[0]
433 scm = get_scm(repopath)[0]
431 repo = get_repo(path=repopath, alias=scm)
434 repo = get_repo(path=repopath, alias=scm)
432 tip = repo.get_changeset()
435 tip = repo.get_changeset()
433 return (tip.revision, tip.short_id)
436 return (tip.revision, tip.short_id)
434 except Exception, err:
437 except Exception, err:
435 if not quiet:
438 if not quiet:
436 print ("Cannot retrieve rhodecode's revision. Original error "
439 print ("Cannot retrieve rhodecode's revision. Original error "
437 "was: %s" % err)
440 "was: %s" % err)
438 return None
441 return None
439
442
443
440 def extract_mentioned_users(s):
444 def extract_mentioned_users(s):
441 """
445 """
442 Returns unique usernames from given string s that have @mention
446 Returns unique usernames from given string s that have @mention
443
447
444 :param s: string to get mentions
448 :param s: string to get mentions
445 """
449 """
446 usrs = {}
450 usrs = {}
447 for username in re.findall(r'(?:^@|\s@)(\w+)', s):
451 for username in re.findall(r'(?:^@|\s@)(\w+)', s):
448 usrs[username] = username
452 usrs[username] = username
449
453
450 return sorted(usrs.keys())
454 return sorted(usrs.keys())
@@ -1,701 +1,774 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import random
26 import random
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import hashlib
29 import hashlib
30
30
31 from tempfile import _RandomNameSequence
31 from tempfile import _RandomNameSequence
32 from decorator import decorator
32 from decorator import decorator
33
33
34 from pylons import config, session, url, request
34 from pylons import config, url, request
35 from pylons.controllers.util import abort, redirect
35 from pylons.controllers.util import abort, redirect
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40
40
41 if __platform__ in PLATFORM_WIN:
41 if __platform__ in PLATFORM_WIN:
42 from hashlib import sha256
42 from hashlib import sha256
43 if __platform__ in PLATFORM_OTHERS:
43 if __platform__ in PLATFORM_OTHERS:
44 import bcrypt
44 import bcrypt
45
45
46 from rhodecode.lib import str2bool, safe_unicode
46 from rhodecode.lib import str2bool, safe_unicode
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 from rhodecode.lib.utils import get_repo_slug
48 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
49 from rhodecode.lib.auth_ldap import AuthLdap
49 from rhodecode.lib.auth_ldap import AuthLdap
50
50
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.user import UserModel
52 from rhodecode.model.user import UserModel
53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class PasswordGenerator(object):
58 class PasswordGenerator(object):
59 """
59 """
60 This is a simple class for generating password from different sets of
60 This is a simple class for generating password from different sets of
61 characters
61 characters
62 usage::
62 usage::
63
63
64 passwd_gen = PasswordGenerator()
64 passwd_gen = PasswordGenerator()
65 #print 8-letter password containing only big and small letters
65 #print 8-letter password containing only big and small letters
66 of alphabet
66 of alphabet
67 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
67 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
68 """
68 """
69 ALPHABETS_NUM = r'''1234567890'''
69 ALPHABETS_NUM = r'''1234567890'''
70 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
70 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
71 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
71 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
72 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
72 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
73 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
73 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
74 + ALPHABETS_NUM + ALPHABETS_SPECIAL
74 + ALPHABETS_NUM + ALPHABETS_SPECIAL
75 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
75 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
76 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
76 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
77 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
77 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
78 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
78 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
79
79
80 def __init__(self, passwd=''):
80 def __init__(self, passwd=''):
81 self.passwd = passwd
81 self.passwd = passwd
82
82
83 def gen_password(self, len, type):
83 def gen_password(self, length, type_):
84 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
84 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
85 return self.passwd
85 return self.passwd
86
86
87
87
88 class RhodeCodeCrypto(object):
88 class RhodeCodeCrypto(object):
89
89
90 @classmethod
90 @classmethod
91 def hash_string(cls, str_):
91 def hash_string(cls, str_):
92 """
92 """
93 Cryptographic function used for password hashing based on pybcrypt
93 Cryptographic function used for password hashing based on pybcrypt
94 or pycrypto in windows
94 or pycrypto in windows
95
95
96 :param password: password to hash
96 :param password: password to hash
97 """
97 """
98 if __platform__ in PLATFORM_WIN:
98 if __platform__ in PLATFORM_WIN:
99 return sha256(str_).hexdigest()
99 return sha256(str_).hexdigest()
100 elif __platform__ in PLATFORM_OTHERS:
100 elif __platform__ in PLATFORM_OTHERS:
101 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
101 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
102 else:
102 else:
103 raise Exception('Unknown or unsupported platform %s' \
103 raise Exception('Unknown or unsupported platform %s' \
104 % __platform__)
104 % __platform__)
105
105
106 @classmethod
106 @classmethod
107 def hash_check(cls, password, hashed):
107 def hash_check(cls, password, hashed):
108 """
108 """
109 Checks matching password with it's hashed value, runs different
109 Checks matching password with it's hashed value, runs different
110 implementation based on platform it runs on
110 implementation based on platform it runs on
111
111
112 :param password: password
112 :param password: password
113 :param hashed: password in hashed form
113 :param hashed: password in hashed form
114 """
114 """
115
115
116 if __platform__ in PLATFORM_WIN:
116 if __platform__ in PLATFORM_WIN:
117 return sha256(password).hexdigest() == hashed
117 return sha256(password).hexdigest() == hashed
118 elif __platform__ in PLATFORM_OTHERS:
118 elif __platform__ in PLATFORM_OTHERS:
119 return bcrypt.hashpw(password, hashed) == hashed
119 return bcrypt.hashpw(password, hashed) == hashed
120 else:
120 else:
121 raise Exception('Unknown or unsupported platform %s' \
121 raise Exception('Unknown or unsupported platform %s' \
122 % __platform__)
122 % __platform__)
123
123
124
124
125 def get_crypt_password(password):
125 def get_crypt_password(password):
126 return RhodeCodeCrypto.hash_string(password)
126 return RhodeCodeCrypto.hash_string(password)
127
127
128
128
129 def check_password(password, hashed):
129 def check_password(password, hashed):
130 return RhodeCodeCrypto.hash_check(password, hashed)
130 return RhodeCodeCrypto.hash_check(password, hashed)
131
131
132
132
133 def generate_api_key(str_, salt=None):
133 def generate_api_key(str_, salt=None):
134 """
134 """
135 Generates API KEY from given string
135 Generates API KEY from given string
136
136
137 :param str_:
137 :param str_:
138 :param salt:
138 :param salt:
139 """
139 """
140
140
141 if salt is None:
141 if salt is None:
142 salt = _RandomNameSequence().next()
142 salt = _RandomNameSequence().next()
143
143
144 return hashlib.sha1(str_ + salt).hexdigest()
144 return hashlib.sha1(str_ + salt).hexdigest()
145
145
146
146
147 def authfunc(environ, username, password):
147 def authfunc(environ, username, password):
148 """
148 """
149 Dummy authentication wrapper function used in Mercurial and Git for
149 Dummy authentication wrapper function used in Mercurial and Git for
150 access control.
150 access control.
151
151
152 :param environ: needed only for using in Basic auth
152 :param environ: needed only for using in Basic auth
153 """
153 """
154 return authenticate(username, password)
154 return authenticate(username, password)
155
155
156
156
157 def authenticate(username, password):
157 def authenticate(username, password):
158 """
158 """
159 Authentication function used for access control,
159 Authentication function used for access control,
160 firstly checks for db authentication then if ldap is enabled for ldap
160 firstly checks for db authentication then if ldap is enabled for ldap
161 authentication, also creates ldap user if not in database
161 authentication, also creates ldap user if not in database
162
162
163 :param username: username
163 :param username: username
164 :param password: password
164 :param password: password
165 """
165 """
166
166
167 user_model = UserModel()
167 user_model = UserModel()
168 user = User.get_by_username(username)
168 user = User.get_by_username(username)
169
169
170 log.debug('Authenticating user using RhodeCode account')
170 log.debug('Authenticating user using RhodeCode account')
171 if user is not None and not user.ldap_dn:
171 if user is not None and not user.ldap_dn:
172 if user.active:
172 if user.active:
173 if user.username == 'default' and user.active:
173 if user.username == 'default' and user.active:
174 log.info('user %s authenticated correctly as anonymous user',
174 log.info('user %s authenticated correctly as anonymous user',
175 username)
175 username)
176 return True
176 return True
177
177
178 elif user.username == username and check_password(password,
178 elif user.username == username and check_password(password,
179 user.password):
179 user.password):
180 log.info('user %s authenticated correctly' % username)
180 log.info('user %s authenticated correctly' % username)
181 return True
181 return True
182 else:
182 else:
183 log.warning('user %s is disabled' % username)
183 log.warning('user %s is disabled' % username)
184
184
185 else:
185 else:
186 log.debug('Regular authentication failed')
186 log.debug('Regular authentication failed')
187 user_obj = User.get_by_username(username, case_insensitive=True)
187 user_obj = User.get_by_username(username, case_insensitive=True)
188
188
189 if user_obj is not None and not user_obj.ldap_dn:
189 if user_obj is not None and not user_obj.ldap_dn:
190 log.debug('this user already exists as non ldap')
190 log.debug('this user already exists as non ldap')
191 return False
191 return False
192
192
193 ldap_settings = RhodeCodeSetting.get_ldap_settings()
193 ldap_settings = RhodeCodeSetting.get_ldap_settings()
194 #======================================================================
194 #======================================================================
195 # FALLBACK TO LDAP AUTH IF ENABLE
195 # FALLBACK TO LDAP AUTH IF ENABLE
196 #======================================================================
196 #======================================================================
197 if str2bool(ldap_settings.get('ldap_active')):
197 if str2bool(ldap_settings.get('ldap_active')):
198 log.debug("Authenticating user using ldap")
198 log.debug("Authenticating user using ldap")
199 kwargs = {
199 kwargs = {
200 'server': ldap_settings.get('ldap_host', ''),
200 'server': ldap_settings.get('ldap_host', ''),
201 'base_dn': ldap_settings.get('ldap_base_dn', ''),
201 'base_dn': ldap_settings.get('ldap_base_dn', ''),
202 'port': ldap_settings.get('ldap_port'),
202 'port': ldap_settings.get('ldap_port'),
203 'bind_dn': ldap_settings.get('ldap_dn_user'),
203 'bind_dn': ldap_settings.get('ldap_dn_user'),
204 'bind_pass': ldap_settings.get('ldap_dn_pass'),
204 'bind_pass': ldap_settings.get('ldap_dn_pass'),
205 'tls_kind': ldap_settings.get('ldap_tls_kind'),
205 'tls_kind': ldap_settings.get('ldap_tls_kind'),
206 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
206 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
207 'ldap_filter': ldap_settings.get('ldap_filter'),
207 'ldap_filter': ldap_settings.get('ldap_filter'),
208 'search_scope': ldap_settings.get('ldap_search_scope'),
208 'search_scope': ldap_settings.get('ldap_search_scope'),
209 'attr_login': ldap_settings.get('ldap_attr_login'),
209 'attr_login': ldap_settings.get('ldap_attr_login'),
210 'ldap_version': 3,
210 'ldap_version': 3,
211 }
211 }
212 log.debug('Checking for ldap authentication')
212 log.debug('Checking for ldap authentication')
213 try:
213 try:
214 aldap = AuthLdap(**kwargs)
214 aldap = AuthLdap(**kwargs)
215 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
215 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
216 password)
216 password)
217 log.debug('Got ldap DN response %s' % user_dn)
217 log.debug('Got ldap DN response %s' % user_dn)
218
218
219 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
219 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
220 .get(k), [''])[0]
220 .get(k), [''])[0]
221
221
222 user_attrs = {
222 user_attrs = {
223 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
223 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
224 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
224 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
225 'email': get_ldap_attr('ldap_attr_email'),
225 'email': get_ldap_attr('ldap_attr_email'),
226 }
226 }
227
227
228 if user_model.create_ldap(username, password, user_dn,
228 if user_model.create_ldap(username, password, user_dn,
229 user_attrs):
229 user_attrs):
230 log.info('created new ldap user %s' % username)
230 log.info('created new ldap user %s' % username)
231
231
232 Session.commit()
232 Session.commit()
233 return True
233 return True
234 except (LdapUsernameError, LdapPasswordError,):
234 except (LdapUsernameError, LdapPasswordError,):
235 pass
235 pass
236 except (Exception,):
236 except (Exception,):
237 log.error(traceback.format_exc())
237 log.error(traceback.format_exc())
238 pass
238 pass
239 return False
239 return False
240
240
241
241
242 def login_container_auth(username):
242 def login_container_auth(username):
243 user = User.get_by_username(username)
243 user = User.get_by_username(username)
244 if user is None:
244 if user is None:
245 user_attrs = {
245 user_attrs = {
246 'name': username,
246 'name': username,
247 'lastname': None,
247 'lastname': None,
248 'email': None,
248 'email': None,
249 }
249 }
250 user = UserModel().create_for_container_auth(username, user_attrs)
250 user = UserModel().create_for_container_auth(username, user_attrs)
251 if not user:
251 if not user:
252 return None
252 return None
253 log.info('User %s was created by container authentication' % username)
253 log.info('User %s was created by container authentication' % username)
254
254
255 if not user.active:
255 if not user.active:
256 return None
256 return None
257
257
258 user.update_lastlogin()
258 user.update_lastlogin()
259 Session.commit()
259 Session.commit()
260
260
261 log.debug('User %s is now logged in by container authentication',
261 log.debug('User %s is now logged in by container authentication',
262 user.username)
262 user.username)
263 return user
263 return user
264
264
265
265
266 def get_container_username(environ, config):
266 def get_container_username(environ, config):
267 username = None
267 username = None
268
268
269 if str2bool(config.get('container_auth_enabled', False)):
269 if str2bool(config.get('container_auth_enabled', False)):
270 from paste.httpheaders import REMOTE_USER
270 from paste.httpheaders import REMOTE_USER
271 username = REMOTE_USER(environ)
271 username = REMOTE_USER(environ)
272
272
273 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
273 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
274 username = environ.get('HTTP_X_FORWARDED_USER')
274 username = environ.get('HTTP_X_FORWARDED_USER')
275
275
276 if username:
276 if username:
277 # Removing realm and domain from username
277 # Removing realm and domain from username
278 username = username.partition('@')[0]
278 username = username.partition('@')[0]
279 username = username.rpartition('\\')[2]
279 username = username.rpartition('\\')[2]
280 log.debug('Received username %s from container' % username)
280 log.debug('Received username %s from container' % username)
281
281
282 return username
282 return username
283
283
284
284
285 class AuthUser(object):
285 class AuthUser(object):
286 """
286 """
287 A simple object that handles all attributes of user in RhodeCode
287 A simple object that handles all attributes of user in RhodeCode
288
288
289 It does lookup based on API key,given user, or user present in session
289 It does lookup based on API key,given user, or user present in session
290 Then it fills all required information for such user. It also checks if
290 Then it fills all required information for such user. It also checks if
291 anonymous access is enabled and if so, it returns default user as logged
291 anonymous access is enabled and if so, it returns default user as logged
292 in
292 in
293 """
293 """
294
294
295 def __init__(self, user_id=None, api_key=None, username=None):
295 def __init__(self, user_id=None, api_key=None, username=None):
296
296
297 self.user_id = user_id
297 self.user_id = user_id
298 self.api_key = None
298 self.api_key = None
299 self.username = username
299 self.username = username
300
300
301 self.name = ''
301 self.name = ''
302 self.lastname = ''
302 self.lastname = ''
303 self.email = ''
303 self.email = ''
304 self.is_authenticated = False
304 self.is_authenticated = False
305 self.admin = False
305 self.admin = False
306 self.permissions = {}
306 self.permissions = {}
307 self._api_key = api_key
307 self._api_key = api_key
308 self.propagate_data()
308 self.propagate_data()
309 self._instance = None
309 self._instance = None
310
310
311 def propagate_data(self):
311 def propagate_data(self):
312 user_model = UserModel()
312 user_model = UserModel()
313 self.anonymous_user = User.get_by_username('default', cache=True)
313 self.anonymous_user = User.get_by_username('default', cache=True)
314 is_user_loaded = False
314 is_user_loaded = False
315
315
316 # try go get user by api key
316 # try go get user by api key
317 if self._api_key and self._api_key != self.anonymous_user.api_key:
317 if self._api_key and self._api_key != self.anonymous_user.api_key:
318 log.debug('Auth User lookup by API KEY %s' % self._api_key)
318 log.debug('Auth User lookup by API KEY %s' % self._api_key)
319 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
319 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
320 # lookup by userid
320 # lookup by userid
321 elif (self.user_id is not None and
321 elif (self.user_id is not None and
322 self.user_id != self.anonymous_user.user_id):
322 self.user_id != self.anonymous_user.user_id):
323 log.debug('Auth User lookup by USER ID %s' % self.user_id)
323 log.debug('Auth User lookup by USER ID %s' % self.user_id)
324 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
324 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
325 # lookup by username
325 # lookup by username
326 elif self.username and \
326 elif self.username and \
327 str2bool(config.get('container_auth_enabled', False)):
327 str2bool(config.get('container_auth_enabled', False)):
328
328
329 log.debug('Auth User lookup by USER NAME %s' % self.username)
329 log.debug('Auth User lookup by USER NAME %s' % self.username)
330 dbuser = login_container_auth(self.username)
330 dbuser = login_container_auth(self.username)
331 if dbuser is not None:
331 if dbuser is not None:
332 for k, v in dbuser.get_dict().items():
332 for k, v in dbuser.get_dict().items():
333 setattr(self, k, v)
333 setattr(self, k, v)
334 self.set_authenticated()
334 self.set_authenticated()
335 is_user_loaded = True
335 is_user_loaded = True
336
336
337 if not is_user_loaded:
337 if not is_user_loaded:
338 # if we cannot authenticate user try anonymous
338 # if we cannot authenticate user try anonymous
339 if self.anonymous_user.active is True:
339 if self.anonymous_user.active is True:
340 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
340 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
341 # then we set this user is logged in
341 # then we set this user is logged in
342 self.is_authenticated = True
342 self.is_authenticated = True
343 else:
343 else:
344 self.user_id = None
344 self.user_id = None
345 self.username = None
345 self.username = None
346 self.is_authenticated = False
346 self.is_authenticated = False
347
347
348 if not self.username:
348 if not self.username:
349 self.username = 'None'
349 self.username = 'None'
350
350
351 log.debug('Auth User is now %s' % self)
351 log.debug('Auth User is now %s' % self)
352 user_model.fill_perms(self)
352 user_model.fill_perms(self)
353
353
354 @property
354 @property
355 def is_admin(self):
355 def is_admin(self):
356 return self.admin
356 return self.admin
357
357
358 def __repr__(self):
358 def __repr__(self):
359 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
359 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
360 self.is_authenticated)
360 self.is_authenticated)
361
361
362 def set_authenticated(self, authenticated=True):
362 def set_authenticated(self, authenticated=True):
363 if self.user_id != self.anonymous_user.user_id:
363 if self.user_id != self.anonymous_user.user_id:
364 self.is_authenticated = authenticated
364 self.is_authenticated = authenticated
365
365
366 def get_cookie_store(self):
366 def get_cookie_store(self):
367 return {'username': self.username,
367 return {'username': self.username,
368 'user_id': self.user_id,
368 'user_id': self.user_id,
369 'is_authenticated': self.is_authenticated}
369 'is_authenticated': self.is_authenticated}
370
370
371 @classmethod
371 @classmethod
372 def from_cookie_store(cls, cookie_store):
372 def from_cookie_store(cls, cookie_store):
373 user_id = cookie_store.get('user_id')
373 user_id = cookie_store.get('user_id')
374 username = cookie_store.get('username')
374 username = cookie_store.get('username')
375 api_key = cookie_store.get('api_key')
375 api_key = cookie_store.get('api_key')
376 return AuthUser(user_id, api_key, username)
376 return AuthUser(user_id, api_key, username)
377
377
378
378
379 def set_available_permissions(config):
379 def set_available_permissions(config):
380 """
380 """
381 This function will propagate pylons globals with all available defined
381 This function will propagate pylons globals with all available defined
382 permission given in db. We don't want to check each time from db for new
382 permission given in db. We don't want to check each time from db for new
383 permissions since adding a new permission also requires application restart
383 permissions since adding a new permission also requires application restart
384 ie. to decorate new views with the newly created permission
384 ie. to decorate new views with the newly created permission
385
385
386 :param config: current pylons config instance
386 :param config: current pylons config instance
387
387
388 """
388 """
389 log.info('getting information about all available permissions')
389 log.info('getting information about all available permissions')
390 try:
390 try:
391 sa = meta.Session
391 sa = meta.Session
392 all_perms = sa.query(Permission).all()
392 all_perms = sa.query(Permission).all()
393 except Exception:
393 except Exception:
394 pass
394 pass
395 finally:
395 finally:
396 meta.Session.remove()
396 meta.Session.remove()
397
397
398 config['available_permissions'] = [x.permission_name for x in all_perms]
398 config['available_permissions'] = [x.permission_name for x in all_perms]
399
399
400
400
401 #==============================================================================
401 #==============================================================================
402 # CHECK DECORATORS
402 # CHECK DECORATORS
403 #==============================================================================
403 #==============================================================================
404 class LoginRequired(object):
404 class LoginRequired(object):
405 """
405 """
406 Must be logged in to execute this function else
406 Must be logged in to execute this function else
407 redirect to login page
407 redirect to login page
408
408
409 :param api_access: if enabled this checks only for valid auth token
409 :param api_access: if enabled this checks only for valid auth token
410 and grants access based on valid token
410 and grants access based on valid token
411 """
411 """
412
412
413 def __init__(self, api_access=False):
413 def __init__(self, api_access=False):
414 self.api_access = api_access
414 self.api_access = api_access
415
415
416 def __call__(self, func):
416 def __call__(self, func):
417 return decorator(self.__wrapper, func)
417 return decorator(self.__wrapper, func)
418
418
419 def __wrapper(self, func, *fargs, **fkwargs):
419 def __wrapper(self, func, *fargs, **fkwargs):
420 cls = fargs[0]
420 cls = fargs[0]
421 user = cls.rhodecode_user
421 user = cls.rhodecode_user
422
422
423 api_access_ok = False
423 api_access_ok = False
424 if self.api_access:
424 if self.api_access:
425 log.debug('Checking API KEY access for %s' % cls)
425 log.debug('Checking API KEY access for %s' % cls)
426 if user.api_key == request.GET.get('api_key'):
426 if user.api_key == request.GET.get('api_key'):
427 api_access_ok = True
427 api_access_ok = True
428 else:
428 else:
429 log.debug("API KEY token not valid")
429 log.debug("API KEY token not valid")
430
430
431 log.debug('Checking if %s is authenticated @ %s' % (user.username, cls))
431 log.debug('Checking if %s is authenticated @ %s' % (user.username, cls))
432 if user.is_authenticated or api_access_ok:
432 if user.is_authenticated or api_access_ok:
433 log.debug('user %s is authenticated' % user.username)
433 log.debug('user %s is authenticated' % user.username)
434 return func(*fargs, **fkwargs)
434 return func(*fargs, **fkwargs)
435 else:
435 else:
436 log.warn('user %s NOT authenticated' % user.username)
436 log.warn('user %s NOT authenticated' % user.username)
437 p = url.current()
437 p = url.current()
438
438
439 log.debug('redirecting to login page with %s' % p)
439 log.debug('redirecting to login page with %s' % p)
440 return redirect(url('login_home', came_from=p))
440 return redirect(url('login_home', came_from=p))
441
441
442
442
443 class NotAnonymous(object):
443 class NotAnonymous(object):
444 """
444 """
445 Must be logged in to execute this function else
445 Must be logged in to execute this function else
446 redirect to login page"""
446 redirect to login page"""
447
447
448 def __call__(self, func):
448 def __call__(self, func):
449 return decorator(self.__wrapper, func)
449 return decorator(self.__wrapper, func)
450
450
451 def __wrapper(self, func, *fargs, **fkwargs):
451 def __wrapper(self, func, *fargs, **fkwargs):
452 cls = fargs[0]
452 cls = fargs[0]
453 self.user = cls.rhodecode_user
453 self.user = cls.rhodecode_user
454
454
455 log.debug('Checking if user is not anonymous @%s' % cls)
455 log.debug('Checking if user is not anonymous @%s' % cls)
456
456
457 anonymous = self.user.username == 'default'
457 anonymous = self.user.username == 'default'
458
458
459 if anonymous:
459 if anonymous:
460 p = url.current()
460 p = url.current()
461
461
462 import rhodecode.lib.helpers as h
462 import rhodecode.lib.helpers as h
463 h.flash(_('You need to be a registered user to '
463 h.flash(_('You need to be a registered user to '
464 'perform this action'),
464 'perform this action'),
465 category='warning')
465 category='warning')
466 return redirect(url('login_home', came_from=p))
466 return redirect(url('login_home', came_from=p))
467 else:
467 else:
468 return func(*fargs, **fkwargs)
468 return func(*fargs, **fkwargs)
469
469
470
470
471 class PermsDecorator(object):
471 class PermsDecorator(object):
472 """Base class for controller decorators"""
472 """Base class for controller decorators"""
473
473
474 def __init__(self, *required_perms):
474 def __init__(self, *required_perms):
475 available_perms = config['available_permissions']
475 available_perms = config['available_permissions']
476 for perm in required_perms:
476 for perm in required_perms:
477 if perm not in available_perms:
477 if perm not in available_perms:
478 raise Exception("'%s' permission is not defined" % perm)
478 raise Exception("'%s' permission is not defined" % perm)
479 self.required_perms = set(required_perms)
479 self.required_perms = set(required_perms)
480 self.user_perms = None
480 self.user_perms = None
481
481
482 def __call__(self, func):
482 def __call__(self, func):
483 return decorator(self.__wrapper, func)
483 return decorator(self.__wrapper, func)
484
484
485 def __wrapper(self, func, *fargs, **fkwargs):
485 def __wrapper(self, func, *fargs, **fkwargs):
486 cls = fargs[0]
486 cls = fargs[0]
487 self.user = cls.rhodecode_user
487 self.user = cls.rhodecode_user
488 self.user_perms = self.user.permissions
488 self.user_perms = self.user.permissions
489 log.debug('checking %s permissions %s for %s %s',
489 log.debug('checking %s permissions %s for %s %s',
490 self.__class__.__name__, self.required_perms, cls,
490 self.__class__.__name__, self.required_perms, cls,
491 self.user)
491 self.user)
492
492
493 if self.check_permissions():
493 if self.check_permissions():
494 log.debug('Permission granted for %s %s' % (cls, self.user))
494 log.debug('Permission granted for %s %s' % (cls, self.user))
495 return func(*fargs, **fkwargs)
495 return func(*fargs, **fkwargs)
496
496
497 else:
497 else:
498 log.warning('Permission denied for %s %s' % (cls, self.user))
498 log.warning('Permission denied for %s %s' % (cls, self.user))
499 anonymous = self.user.username == 'default'
499 anonymous = self.user.username == 'default'
500
500
501 if anonymous:
501 if anonymous:
502 p = url.current()
502 p = url.current()
503
503
504 import rhodecode.lib.helpers as h
504 import rhodecode.lib.helpers as h
505 h.flash(_('You need to be a signed in to '
505 h.flash(_('You need to be a signed in to '
506 'view this page'),
506 'view this page'),
507 category='warning')
507 category='warning')
508 return redirect(url('login_home', came_from=p))
508 return redirect(url('login_home', came_from=p))
509
509
510 else:
510 else:
511 # redirect with forbidden ret code
511 # redirect with forbidden ret code
512 return abort(403)
512 return abort(403)
513
513
514 def check_permissions(self):
514 def check_permissions(self):
515 """Dummy function for overriding"""
515 """Dummy function for overriding"""
516 raise Exception('You have to write this function in child class')
516 raise Exception('You have to write this function in child class')
517
517
518
518
519 class HasPermissionAllDecorator(PermsDecorator):
519 class HasPermissionAllDecorator(PermsDecorator):
520 """
520 """
521 Checks for access permission for all given predicates. All of them
521 Checks for access permission for all given predicates. All of them
522 have to be meet in order to fulfill the request
522 have to be meet in order to fulfill the request
523 """
523 """
524
524
525 def check_permissions(self):
525 def check_permissions(self):
526 if self.required_perms.issubset(self.user_perms.get('global')):
526 if self.required_perms.issubset(self.user_perms.get('global')):
527 return True
527 return True
528 return False
528 return False
529
529
530
530
531 class HasPermissionAnyDecorator(PermsDecorator):
531 class HasPermissionAnyDecorator(PermsDecorator):
532 """
532 """
533 Checks for access permission for any of given predicates. In order to
533 Checks for access permission for any of given predicates. In order to
534 fulfill the request any of predicates must be meet
534 fulfill the request any of predicates must be meet
535 """
535 """
536
536
537 def check_permissions(self):
537 def check_permissions(self):
538 if self.required_perms.intersection(self.user_perms.get('global')):
538 if self.required_perms.intersection(self.user_perms.get('global')):
539 return True
539 return True
540 return False
540 return False
541
541
542
542
543 class HasRepoPermissionAllDecorator(PermsDecorator):
543 class HasRepoPermissionAllDecorator(PermsDecorator):
544 """
544 """
545 Checks for access permission for all given predicates for specific
545 Checks for access permission for all given predicates for specific
546 repository. All of them have to be meet in order to fulfill the request
546 repository. All of them have to be meet in order to fulfill the request
547 """
547 """
548
548
549 def check_permissions(self):
549 def check_permissions(self):
550 repo_name = get_repo_slug(request)
550 repo_name = get_repo_slug(request)
551 try:
551 try:
552 user_perms = set([self.user_perms['repositories'][repo_name]])
552 user_perms = set([self.user_perms['repositories'][repo_name]])
553 except KeyError:
553 except KeyError:
554 return False
554 return False
555 if self.required_perms.issubset(user_perms):
555 if self.required_perms.issubset(user_perms):
556 return True
556 return True
557 return False
557 return False
558
558
559
559
560 class HasRepoPermissionAnyDecorator(PermsDecorator):
560 class HasRepoPermissionAnyDecorator(PermsDecorator):
561 """
561 """
562 Checks for access permission for any of given predicates for specific
562 Checks for access permission for any of given predicates for specific
563 repository. In order to fulfill the request any of predicates must be meet
563 repository. In order to fulfill the request any of predicates must be meet
564 """
564 """
565
565
566 def check_permissions(self):
566 def check_permissions(self):
567 repo_name = get_repo_slug(request)
567 repo_name = get_repo_slug(request)
568
568
569 try:
569 try:
570 user_perms = set([self.user_perms['repositories'][repo_name]])
570 user_perms = set([self.user_perms['repositories'][repo_name]])
571 except KeyError:
571 except KeyError:
572 return False
572 return False
573 if self.required_perms.intersection(user_perms):
573 if self.required_perms.intersection(user_perms):
574 return True
574 return True
575 return False
575 return False
576
576
577
577
578 class HasReposGroupPermissionAllDecorator(PermsDecorator):
579 """
580 Checks for access permission for all given predicates for specific
581 repository. All of them have to be meet in order to fulfill the request
582 """
583
584 def check_permissions(self):
585 group_name = get_repos_group_slug(request)
586 try:
587 user_perms = set([self.user_perms['repositories_groups'][group_name]])
588 except KeyError:
589 return False
590 if self.required_perms.issubset(user_perms):
591 return True
592 return False
593
594
595 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
596 """
597 Checks for access permission for any of given predicates for specific
598 repository. In order to fulfill the request any of predicates must be meet
599 """
600
601 def check_permissions(self):
602 group_name = get_repos_group_slug(request)
603
604 try:
605 user_perms = set([self.user_perms['repositories_groups'][group_name]])
606 except KeyError:
607 return False
608 if self.required_perms.intersection(user_perms):
609 return True
610 return False
611
612
578 #==============================================================================
613 #==============================================================================
579 # CHECK FUNCTIONS
614 # CHECK FUNCTIONS
580 #==============================================================================
615 #==============================================================================
581 class PermsFunction(object):
616 class PermsFunction(object):
582 """Base function for other check functions"""
617 """Base function for other check functions"""
583
618
584 def __init__(self, *perms):
619 def __init__(self, *perms):
585 available_perms = config['available_permissions']
620 available_perms = config['available_permissions']
586
621
587 for perm in perms:
622 for perm in perms:
588 if perm not in available_perms:
623 if perm not in available_perms:
589 raise Exception("'%s' permission in not defined" % perm)
624 raise Exception("'%s' permission in not defined" % perm)
590 self.required_perms = set(perms)
625 self.required_perms = set(perms)
591 self.user_perms = None
626 self.user_perms = None
592 self.granted_for = ''
627 self.granted_for = ''
593 self.repo_name = None
628 self.repo_name = None
594
629
595 def __call__(self, check_Location=''):
630 def __call__(self, check_Location=''):
596 user = request.user
631 user = request.user
597 if not user:
632 if not user:
598 return False
633 return False
599 self.user_perms = user.permissions
634 self.user_perms = user.permissions
600 self.granted_for = user
635 self.granted_for = user
601 log.debug('checking %s %s %s', self.__class__.__name__,
636 log.debug('checking %s %s %s', self.__class__.__name__,
602 self.required_perms, user)
637 self.required_perms, user)
603
638
604 if self.check_permissions():
639 if self.check_permissions():
605 log.debug('Permission granted %s @ %s', self.granted_for,
640 log.debug('Permission granted %s @ %s', self.granted_for,
606 check_Location or 'unspecified location')
641 check_Location or 'unspecified location')
607 return True
642 return True
608
643
609 else:
644 else:
610 log.warning('Permission denied for %s @ %s', self.granted_for,
645 log.warning('Permission denied for %s @ %s', self.granted_for,
611 check_Location or 'unspecified location')
646 check_Location or 'unspecified location')
612 return False
647 return False
613
648
614 def check_permissions(self):
649 def check_permissions(self):
615 """Dummy function for overriding"""
650 """Dummy function for overriding"""
616 raise Exception('You have to write this function in child class')
651 raise Exception('You have to write this function in child class')
617
652
618
653
619 class HasPermissionAll(PermsFunction):
654 class HasPermissionAll(PermsFunction):
620 def check_permissions(self):
655 def check_permissions(self):
621 if self.required_perms.issubset(self.user_perms.get('global')):
656 if self.required_perms.issubset(self.user_perms.get('global')):
622 return True
657 return True
623 return False
658 return False
624
659
625
660
626 class HasPermissionAny(PermsFunction):
661 class HasPermissionAny(PermsFunction):
627 def check_permissions(self):
662 def check_permissions(self):
628 if self.required_perms.intersection(self.user_perms.get('global')):
663 if self.required_perms.intersection(self.user_perms.get('global')):
629 return True
664 return True
630 return False
665 return False
631
666
632
667
633 class HasRepoPermissionAll(PermsFunction):
668 class HasRepoPermissionAll(PermsFunction):
634
669
635 def __call__(self, repo_name=None, check_Location=''):
670 def __call__(self, repo_name=None, check_Location=''):
636 self.repo_name = repo_name
671 self.repo_name = repo_name
637 return super(HasRepoPermissionAll, self).__call__(check_Location)
672 return super(HasRepoPermissionAll, self).__call__(check_Location)
638
673
639 def check_permissions(self):
674 def check_permissions(self):
640 if not self.repo_name:
675 if not self.repo_name:
641 self.repo_name = get_repo_slug(request)
676 self.repo_name = get_repo_slug(request)
642
677
643 try:
678 try:
644 self.user_perms = set([self.user_perms['reposit'
679 self.user_perms = set(
645 'ories'][self.repo_name]])
680 [self.user_perms['repositories'][self.repo_name]]
681 )
646 except KeyError:
682 except KeyError:
647 return False
683 return False
648 self.granted_for = self.repo_name
684 self.granted_for = self.repo_name
649 if self.required_perms.issubset(self.user_perms):
685 if self.required_perms.issubset(self.user_perms):
650 return True
686 return True
651 return False
687 return False
652
688
653
689
654 class HasRepoPermissionAny(PermsFunction):
690 class HasRepoPermissionAny(PermsFunction):
655
691
656 def __call__(self, repo_name=None, check_Location=''):
692 def __call__(self, repo_name=None, check_Location=''):
657 self.repo_name = repo_name
693 self.repo_name = repo_name
658 return super(HasRepoPermissionAny, self).__call__(check_Location)
694 return super(HasRepoPermissionAny, self).__call__(check_Location)
659
695
660 def check_permissions(self):
696 def check_permissions(self):
661 if not self.repo_name:
697 if not self.repo_name:
662 self.repo_name = get_repo_slug(request)
698 self.repo_name = get_repo_slug(request)
663
699
664 try:
700 try:
665 self.user_perms = set([self.user_perms['reposi'
701 self.user_perms = set(
666 'tories'][self.repo_name]])
702 [self.user_perms['repositories'][self.repo_name]]
703 )
667 except KeyError:
704 except KeyError:
668 return False
705 return False
669 self.granted_for = self.repo_name
706 self.granted_for = self.repo_name
670 if self.required_perms.intersection(self.user_perms):
707 if self.required_perms.intersection(self.user_perms):
671 return True
708 return True
672 return False
709 return False
673
710
674
711
712 class HasReposGroupPermissionAny(PermsFunction):
713 def __call__(self, group_name=None, check_Location=''):
714 self.group_name = group_name
715 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
716
717 def check_permissions(self):
718 try:
719 self.user_perms = set(
720 [self.user_perms['repositories_groups'][self.group_name]]
721 )
722 except KeyError:
723 return False
724 self.granted_for = self.repo_name
725 if self.required_perms.intersection(self.user_perms):
726 return True
727 return False
728
729
730 class HasReposGroupPermissionAll(PermsFunction):
731 def __call__(self, group_name=None, check_Location=''):
732 self.group_name = group_name
733 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
734
735 def check_permissions(self):
736 try:
737 self.user_perms = set(
738 [self.user_perms['repositories_groups'][self.group_name]]
739 )
740 except KeyError:
741 return False
742 self.granted_for = self.repo_name
743 if self.required_perms.issubset(self.user_perms):
744 return True
745 return False
746
747
675 #==============================================================================
748 #==============================================================================
676 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
749 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
677 #==============================================================================
750 #==============================================================================
678 class HasPermissionAnyMiddleware(object):
751 class HasPermissionAnyMiddleware(object):
679 def __init__(self, *perms):
752 def __init__(self, *perms):
680 self.required_perms = set(perms)
753 self.required_perms = set(perms)
681
754
682 def __call__(self, user, repo_name):
755 def __call__(self, user, repo_name):
683 usr = AuthUser(user.user_id)
756 usr = AuthUser(user.user_id)
684 try:
757 try:
685 self.user_perms = set([usr.permissions['repositories'][repo_name]])
758 self.user_perms = set([usr.permissions['repositories'][repo_name]])
686 except:
759 except:
687 self.user_perms = set()
760 self.user_perms = set()
688 self.granted_for = ''
761 self.granted_for = ''
689 self.username = user.username
762 self.username = user.username
690 self.repo_name = repo_name
763 self.repo_name = repo_name
691 return self.check_permissions()
764 return self.check_permissions()
692
765
693 def check_permissions(self):
766 def check_permissions(self):
694 log.debug('checking mercurial protocol '
767 log.debug('checking mercurial protocol '
695 'permissions %s for user:%s repository:%s', self.user_perms,
768 'permissions %s for user:%s repository:%s', self.user_perms,
696 self.username, self.repo_name)
769 self.username, self.repo_name)
697 if self.required_perms.intersection(self.user_perms):
770 if self.required_perms.intersection(self.user_perms):
698 log.debug('permission granted')
771 log.debug('permission granted')
699 return True
772 return True
700 log.debug('permission denied')
773 log.debug('permission denied')
701 return False
774 return False
@@ -1,495 +1,500 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.db_manage
3 rhodecode.lib.db_manage
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Database creation, and setup module for RhodeCode. Used for creation
6 Database creation, and setup module for RhodeCode. Used for creation
7 of database as well as for migration operations
7 of database as well as for migration operations
8
8
9 :created_on: Apr 10, 2010
9 :created_on: Apr 10, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import uuid
29 import uuid
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from rhodecode import __dbversion__
33 from rhodecode import __dbversion__
34 from rhodecode.model import meta
34 from rhodecode.model import meta
35
35
36 from rhodecode.model.user import UserModel
36 from rhodecode.model.user import UserModel
37 from rhodecode.lib.utils import ask_ok
37 from rhodecode.lib.utils import ask_ok
38 from rhodecode.model import init_model
38 from rhodecode.model import init_model
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 RhodeCodeSetting, UserToPerm, DbMigrateVersion
40 RhodeCodeSetting, UserToPerm, DbMigrateVersion
41
41
42 from sqlalchemy.engine import create_engine
42 from sqlalchemy.engine import create_engine
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class DbManage(object):
47 class DbManage(object):
48 def __init__(self, log_sql, dbconf, root, tests=False):
48 def __init__(self, log_sql, dbconf, root, tests=False):
49 self.dbname = dbconf.split('/')[-1]
49 self.dbname = dbconf.split('/')[-1]
50 self.tests = tests
50 self.tests = tests
51 self.root = root
51 self.root = root
52 self.dburi = dbconf
52 self.dburi = dbconf
53 self.log_sql = log_sql
53 self.log_sql = log_sql
54 self.db_exists = False
54 self.db_exists = False
55 self.init_db()
55 self.init_db()
56
56
57 def init_db(self):
57 def init_db(self):
58 engine = create_engine(self.dburi, echo=self.log_sql)
58 engine = create_engine(self.dburi, echo=self.log_sql)
59 init_model(engine)
59 init_model(engine)
60 self.sa = meta.Session
60 self.sa = meta.Session
61
61
62 def create_tables(self, override=False):
62 def create_tables(self, override=False):
63 """
63 """
64 Create a auth database
64 Create a auth database
65 """
65 """
66
66
67 log.info("Any existing database is going to be destroyed")
67 log.info("Any existing database is going to be destroyed")
68 if self.tests:
68 if self.tests:
69 destroy = True
69 destroy = True
70 else:
70 else:
71 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
71 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
72 if not destroy:
72 if not destroy:
73 sys.exit()
73 sys.exit()
74 if destroy:
74 if destroy:
75 meta.Base.metadata.drop_all()
75 meta.Base.metadata.drop_all()
76
76
77 checkfirst = not override
77 checkfirst = not override
78 meta.Base.metadata.create_all(checkfirst=checkfirst)
78 meta.Base.metadata.create_all(checkfirst=checkfirst)
79 log.info('Created tables for %s' % self.dbname)
79 log.info('Created tables for %s' % self.dbname)
80
80
81 def set_db_version(self):
81 def set_db_version(self):
82 ver = DbMigrateVersion()
82 ver = DbMigrateVersion()
83 ver.version = __dbversion__
83 ver.version = __dbversion__
84 ver.repository_id = 'rhodecode_db_migrations'
84 ver.repository_id = 'rhodecode_db_migrations'
85 ver.repository_path = 'versions'
85 ver.repository_path = 'versions'
86 self.sa.add(ver)
86 self.sa.add(ver)
87 log.info('db version set to: %s' % __dbversion__)
87 log.info('db version set to: %s' % __dbversion__)
88
88
89 def upgrade(self):
89 def upgrade(self):
90 """
90 """
91 Upgrades given database schema to given revision following
91 Upgrades given database schema to given revision following
92 all needed steps, to perform the upgrade
92 all needed steps, to perform the upgrade
93
93
94 """
94 """
95
95
96 from rhodecode.lib.dbmigrate.migrate.versioning import api
96 from rhodecode.lib.dbmigrate.migrate.versioning import api
97 from rhodecode.lib.dbmigrate.migrate.exceptions import \
97 from rhodecode.lib.dbmigrate.migrate.exceptions import \
98 DatabaseNotControlledError
98 DatabaseNotControlledError
99
99
100 if 'sqlite' in self.dburi:
100 if 'sqlite' in self.dburi:
101 print (
101 print (
102 '********************** WARNING **********************\n'
102 '********************** WARNING **********************\n'
103 'Make sure your version of sqlite is at least 3.7.X. \n'
103 'Make sure your version of sqlite is at least 3.7.X. \n'
104 'Earlier versions are known to fail on some migrations\n'
104 'Earlier versions are known to fail on some migrations\n'
105 '*****************************************************\n'
105 '*****************************************************\n'
106 )
106 )
107 upgrade = ask_ok('You are about to perform database upgrade, make '
107 upgrade = ask_ok('You are about to perform database upgrade, make '
108 'sure You backed up your database before. '
108 'sure You backed up your database before. '
109 'Continue ? [y/n]')
109 'Continue ? [y/n]')
110 if not upgrade:
110 if not upgrade:
111 sys.exit('Nothing done')
111 sys.exit('Nothing done')
112
112
113 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
113 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
114 'rhodecode/lib/dbmigrate')
114 'rhodecode/lib/dbmigrate')
115 db_uri = self.dburi
115 db_uri = self.dburi
116
116
117 try:
117 try:
118 curr_version = api.db_version(db_uri, repository_path)
118 curr_version = api.db_version(db_uri, repository_path)
119 msg = ('Found current database under version'
119 msg = ('Found current database under version'
120 ' control with version %s' % curr_version)
120 ' control with version %s' % curr_version)
121
121
122 except (RuntimeError, DatabaseNotControlledError):
122 except (RuntimeError, DatabaseNotControlledError):
123 curr_version = 1
123 curr_version = 1
124 msg = ('Current database is not under version control. Setting'
124 msg = ('Current database is not under version control. Setting'
125 ' as version %s' % curr_version)
125 ' as version %s' % curr_version)
126 api.version_control(db_uri, repository_path, curr_version)
126 api.version_control(db_uri, repository_path, curr_version)
127
127
128 print (msg)
128 print (msg)
129
129
130 if curr_version == __dbversion__:
130 if curr_version == __dbversion__:
131 sys.exit('This database is already at the newest version')
131 sys.exit('This database is already at the newest version')
132
132
133 #======================================================================
133 #======================================================================
134 # UPGRADE STEPS
134 # UPGRADE STEPS
135 #======================================================================
135 #======================================================================
136 class UpgradeSteps(object):
136 class UpgradeSteps(object):
137 """
137 """
138 Those steps follow schema versions so for example schema
138 Those steps follow schema versions so for example schema
139 for example schema with seq 002 == step_2 and so on.
139 for example schema with seq 002 == step_2 and so on.
140 """
140 """
141
141
142 def __init__(self, klass):
142 def __init__(self, klass):
143 self.klass = klass
143 self.klass = klass
144
144
145 def step_0(self):
145 def step_0(self):
146 # step 0 is the schema upgrade, and than follow proper upgrades
146 # step 0 is the schema upgrade, and than follow proper upgrades
147 print ('attempting to do database upgrade to version %s' \
147 print ('attempting to do database upgrade to version %s' \
148 % __dbversion__)
148 % __dbversion__)
149 api.upgrade(db_uri, repository_path, __dbversion__)
149 api.upgrade(db_uri, repository_path, __dbversion__)
150 print ('Schema upgrade completed')
150 print ('Schema upgrade completed')
151
151
152 def step_1(self):
152 def step_1(self):
153 pass
153 pass
154
154
155 def step_2(self):
155 def step_2(self):
156 print ('Patching repo paths for newer version of RhodeCode')
156 print ('Patching repo paths for newer version of RhodeCode')
157 self.klass.fix_repo_paths()
157 self.klass.fix_repo_paths()
158
158
159 print ('Patching default user of RhodeCode')
159 print ('Patching default user of RhodeCode')
160 self.klass.fix_default_user()
160 self.klass.fix_default_user()
161
161
162 log.info('Changing ui settings')
162 log.info('Changing ui settings')
163 self.klass.create_ui_settings()
163 self.klass.create_ui_settings()
164
164
165 def step_3(self):
165 def step_3(self):
166 print ('Adding additional settings into RhodeCode db')
166 print ('Adding additional settings into RhodeCode db')
167 self.klass.fix_settings()
167 self.klass.fix_settings()
168 print ('Adding ldap defaults')
168 print ('Adding ldap defaults')
169 self.klass.create_ldap_options(skip_existing=True)
169 self.klass.create_ldap_options(skip_existing=True)
170
170
171 def step_4(self):
171 def step_4(self):
172 print ('TODO:')
172 print ('TODO:')
173 raise NotImplementedError()
173 raise NotImplementedError()
174
174
175 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
175 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
176
176
177 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
177 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
178 for step in upgrade_steps:
178 for step in upgrade_steps:
179 print ('performing upgrade step %s' % step)
179 print ('performing upgrade step %s' % step)
180 getattr(UpgradeSteps(self), 'step_%s' % step)()
180 getattr(UpgradeSteps(self), 'step_%s' % step)()
181
181
182 def fix_repo_paths(self):
182 def fix_repo_paths(self):
183 """
183 """
184 Fixes a old rhodecode version path into new one without a '*'
184 Fixes a old rhodecode version path into new one without a '*'
185 """
185 """
186
186
187 paths = self.sa.query(RhodeCodeUi)\
187 paths = self.sa.query(RhodeCodeUi)\
188 .filter(RhodeCodeUi.ui_key == '/')\
188 .filter(RhodeCodeUi.ui_key == '/')\
189 .scalar()
189 .scalar()
190
190
191 paths.ui_value = paths.ui_value.replace('*', '')
191 paths.ui_value = paths.ui_value.replace('*', '')
192
192
193 try:
193 try:
194 self.sa.add(paths)
194 self.sa.add(paths)
195 self.sa.commit()
195 self.sa.commit()
196 except:
196 except:
197 self.sa.rollback()
197 self.sa.rollback()
198 raise
198 raise
199
199
200 def fix_default_user(self):
200 def fix_default_user(self):
201 """
201 """
202 Fixes a old default user with some 'nicer' default values,
202 Fixes a old default user with some 'nicer' default values,
203 used mostly for anonymous access
203 used mostly for anonymous access
204 """
204 """
205 def_user = self.sa.query(User)\
205 def_user = self.sa.query(User)\
206 .filter(User.username == 'default')\
206 .filter(User.username == 'default')\
207 .one()
207 .one()
208
208
209 def_user.name = 'Anonymous'
209 def_user.name = 'Anonymous'
210 def_user.lastname = 'User'
210 def_user.lastname = 'User'
211 def_user.email = 'anonymous@rhodecode.org'
211 def_user.email = 'anonymous@rhodecode.org'
212
212
213 try:
213 try:
214 self.sa.add(def_user)
214 self.sa.add(def_user)
215 self.sa.commit()
215 self.sa.commit()
216 except:
216 except:
217 self.sa.rollback()
217 self.sa.rollback()
218 raise
218 raise
219
219
220 def fix_settings(self):
220 def fix_settings(self):
221 """
221 """
222 Fixes rhodecode settings adds ga_code key for google analytics
222 Fixes rhodecode settings adds ga_code key for google analytics
223 """
223 """
224
224
225 hgsettings3 = RhodeCodeSetting('ga_code', '')
225 hgsettings3 = RhodeCodeSetting('ga_code', '')
226
226
227 try:
227 try:
228 self.sa.add(hgsettings3)
228 self.sa.add(hgsettings3)
229 self.sa.commit()
229 self.sa.commit()
230 except:
230 except:
231 self.sa.rollback()
231 self.sa.rollback()
232 raise
232 raise
233
233
234 def admin_prompt(self, second=False):
234 def admin_prompt(self, second=False):
235 if not self.tests:
235 if not self.tests:
236 import getpass
236 import getpass
237
237
238 def get_password():
238 def get_password():
239 password = getpass.getpass('Specify admin password '
239 password = getpass.getpass('Specify admin password '
240 '(min 6 chars):')
240 '(min 6 chars):')
241 confirm = getpass.getpass('Confirm password:')
241 confirm = getpass.getpass('Confirm password:')
242
242
243 if password != confirm:
243 if password != confirm:
244 log.error('passwords mismatch')
244 log.error('passwords mismatch')
245 return False
245 return False
246 if len(password) < 6:
246 if len(password) < 6:
247 log.error('password is to short use at least 6 characters')
247 log.error('password is to short use at least 6 characters')
248 return False
248 return False
249
249
250 return password
250 return password
251
251
252 username = raw_input('Specify admin username:')
252 username = raw_input('Specify admin username:')
253
253
254 password = get_password()
254 password = get_password()
255 if not password:
255 if not password:
256 #second try
256 #second try
257 password = get_password()
257 password = get_password()
258 if not password:
258 if not password:
259 sys.exit()
259 sys.exit()
260
260
261 email = raw_input('Specify admin email:')
261 email = raw_input('Specify admin email:')
262 self.create_user(username, password, email, True)
262 self.create_user(username, password, email, True)
263 else:
263 else:
264 log.info('creating admin and regular test users')
264 log.info('creating admin and regular test users')
265 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
265 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
266 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
266 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
267 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
267 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
268 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
268 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
269 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
269 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
270
270
271 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
271 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
272 TEST_USER_ADMIN_EMAIL, True)
272 TEST_USER_ADMIN_EMAIL, True)
273
273
274 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
274 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
275 TEST_USER_REGULAR_EMAIL, False)
275 TEST_USER_REGULAR_EMAIL, False)
276
276
277 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
277 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
278 TEST_USER_REGULAR2_EMAIL, False)
278 TEST_USER_REGULAR2_EMAIL, False)
279
279
280 def create_ui_settings(self):
280 def create_ui_settings(self):
281 """
281 """
282 Creates ui settings, fills out hooks
282 Creates ui settings, fills out hooks
283 and disables dotencode
283 and disables dotencode
284 """
284 """
285
285
286 #HOOKS
286 #HOOKS
287 hooks1_key = RhodeCodeUi.HOOK_UPDATE
287 hooks1_key = RhodeCodeUi.HOOK_UPDATE
288 hooks1_ = self.sa.query(RhodeCodeUi)\
288 hooks1_ = self.sa.query(RhodeCodeUi)\
289 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
289 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
290
290
291 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
291 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
292 hooks1.ui_section = 'hooks'
292 hooks1.ui_section = 'hooks'
293 hooks1.ui_key = hooks1_key
293 hooks1.ui_key = hooks1_key
294 hooks1.ui_value = 'hg update >&2'
294 hooks1.ui_value = 'hg update >&2'
295 hooks1.ui_active = False
295 hooks1.ui_active = False
296
296
297 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
297 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
298 hooks2_ = self.sa.query(RhodeCodeUi)\
298 hooks2_ = self.sa.query(RhodeCodeUi)\
299 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
299 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
300
300
301 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
301 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
302 hooks2.ui_section = 'hooks'
302 hooks2.ui_section = 'hooks'
303 hooks2.ui_key = hooks2_key
303 hooks2.ui_key = hooks2_key
304 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
304 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
305
305
306 hooks3 = RhodeCodeUi()
306 hooks3 = RhodeCodeUi()
307 hooks3.ui_section = 'hooks'
307 hooks3.ui_section = 'hooks'
308 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
308 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
309 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
309 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
310
310
311 hooks4 = RhodeCodeUi()
311 hooks4 = RhodeCodeUi()
312 hooks4.ui_section = 'hooks'
312 hooks4.ui_section = 'hooks'
313 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
313 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
314 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
314 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
315
315
316 # For mercurial 1.7 set backward comapatibility with format
316 # For mercurial 1.7 set backward comapatibility with format
317 dotencode_disable = RhodeCodeUi()
317 dotencode_disable = RhodeCodeUi()
318 dotencode_disable.ui_section = 'format'
318 dotencode_disable.ui_section = 'format'
319 dotencode_disable.ui_key = 'dotencode'
319 dotencode_disable.ui_key = 'dotencode'
320 dotencode_disable.ui_value = 'false'
320 dotencode_disable.ui_value = 'false'
321
321
322 # enable largefiles
322 # enable largefiles
323 largefiles = RhodeCodeUi()
323 largefiles = RhodeCodeUi()
324 largefiles.ui_section = 'extensions'
324 largefiles.ui_section = 'extensions'
325 largefiles.ui_key = 'largefiles'
325 largefiles.ui_key = 'largefiles'
326 largefiles.ui_value = ''
326 largefiles.ui_value = ''
327
327
328 self.sa.add(hooks1)
328 self.sa.add(hooks1)
329 self.sa.add(hooks2)
329 self.sa.add(hooks2)
330 self.sa.add(hooks3)
330 self.sa.add(hooks3)
331 self.sa.add(hooks4)
331 self.sa.add(hooks4)
332 self.sa.add(largefiles)
332 self.sa.add(largefiles)
333
333
334 def create_ldap_options(self, skip_existing=False):
334 def create_ldap_options(self, skip_existing=False):
335 """Creates ldap settings"""
335 """Creates ldap settings"""
336
336
337 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
337 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
338 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
338 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
339 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
339 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
340 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
340 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
341 ('ldap_filter', ''), ('ldap_search_scope', ''),
341 ('ldap_filter', ''), ('ldap_search_scope', ''),
342 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
342 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
343 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
343 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
344
344
345 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
345 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
346 log.debug('Skipping option %s' % k)
346 log.debug('Skipping option %s' % k)
347 continue
347 continue
348 setting = RhodeCodeSetting(k, v)
348 setting = RhodeCodeSetting(k, v)
349 self.sa.add(setting)
349 self.sa.add(setting)
350
350
351 def config_prompt(self, test_repo_path='', retries=3):
351 def config_prompt(self, test_repo_path='', retries=3):
352 if retries == 3:
352 if retries == 3:
353 log.info('Setting up repositories config')
353 log.info('Setting up repositories config')
354
354
355 if not self.tests and not test_repo_path:
355 if not self.tests and not test_repo_path:
356 path = raw_input(
356 path = raw_input(
357 'Enter a valid path to store repositories. '
357 'Enter a valid path to store repositories. '
358 'All repositories in that path will be added automatically:'
358 'All repositories in that path will be added automatically:'
359 )
359 )
360 else:
360 else:
361 path = test_repo_path
361 path = test_repo_path
362 path_ok = True
362 path_ok = True
363
363
364 # check proper dir
364 # check proper dir
365 if not os.path.isdir(path):
365 if not os.path.isdir(path):
366 path_ok = False
366 path_ok = False
367 log.error('Given path %s is not a valid directory' % path)
367 log.error('Given path %s is not a valid directory' % path)
368
368
369 # check write access
369 # check write access
370 if not os.access(path, os.W_OK) and path_ok:
370 if not os.access(path, os.W_OK) and path_ok:
371 path_ok = False
371 path_ok = False
372 log.error('No write permission to given path %s' % path)
372 log.error('No write permission to given path %s' % path)
373
373
374 if retries == 0:
374 if retries == 0:
375 sys.exit('max retries reached')
375 sys.exit('max retries reached')
376 if path_ok is False:
376 if path_ok is False:
377 retries -= 1
377 retries -= 1
378 return self.config_prompt(test_repo_path, retries)
378 return self.config_prompt(test_repo_path, retries)
379
379
380 return path
380 return path
381
381
382 def create_settings(self, path):
382 def create_settings(self, path):
383
383
384 self.create_ui_settings()
384 self.create_ui_settings()
385
385
386 #HG UI OPTIONS
386 #HG UI OPTIONS
387 web1 = RhodeCodeUi()
387 web1 = RhodeCodeUi()
388 web1.ui_section = 'web'
388 web1.ui_section = 'web'
389 web1.ui_key = 'push_ssl'
389 web1.ui_key = 'push_ssl'
390 web1.ui_value = 'false'
390 web1.ui_value = 'false'
391
391
392 web2 = RhodeCodeUi()
392 web2 = RhodeCodeUi()
393 web2.ui_section = 'web'
393 web2.ui_section = 'web'
394 web2.ui_key = 'allow_archive'
394 web2.ui_key = 'allow_archive'
395 web2.ui_value = 'gz zip bz2'
395 web2.ui_value = 'gz zip bz2'
396
396
397 web3 = RhodeCodeUi()
397 web3 = RhodeCodeUi()
398 web3.ui_section = 'web'
398 web3.ui_section = 'web'
399 web3.ui_key = 'allow_push'
399 web3.ui_key = 'allow_push'
400 web3.ui_value = '*'
400 web3.ui_value = '*'
401
401
402 web4 = RhodeCodeUi()
402 web4 = RhodeCodeUi()
403 web4.ui_section = 'web'
403 web4.ui_section = 'web'
404 web4.ui_key = 'baseurl'
404 web4.ui_key = 'baseurl'
405 web4.ui_value = '/'
405 web4.ui_value = '/'
406
406
407 paths = RhodeCodeUi()
407 paths = RhodeCodeUi()
408 paths.ui_section = 'paths'
408 paths.ui_section = 'paths'
409 paths.ui_key = '/'
409 paths.ui_key = '/'
410 paths.ui_value = path
410 paths.ui_value = path
411
411
412 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
412 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
413 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
413 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
414 hgsettings3 = RhodeCodeSetting('ga_code', '')
414 hgsettings3 = RhodeCodeSetting('ga_code', '')
415
415
416 self.sa.add(web1)
416 self.sa.add(web1)
417 self.sa.add(web2)
417 self.sa.add(web2)
418 self.sa.add(web3)
418 self.sa.add(web3)
419 self.sa.add(web4)
419 self.sa.add(web4)
420 self.sa.add(paths)
420 self.sa.add(paths)
421 self.sa.add(hgsettings1)
421 self.sa.add(hgsettings1)
422 self.sa.add(hgsettings2)
422 self.sa.add(hgsettings2)
423 self.sa.add(hgsettings3)
423 self.sa.add(hgsettings3)
424
424
425 self.create_ldap_options()
425 self.create_ldap_options()
426
426
427 log.info('created ui config')
427 log.info('created ui config')
428
428
429 def create_user(self, username, password, email='', admin=False):
429 def create_user(self, username, password, email='', admin=False):
430 log.info('creating user %s' % username)
430 log.info('creating user %s' % username)
431 UserModel().create_or_update(username, password, email,
431 UserModel().create_or_update(username, password, email,
432 name='RhodeCode', lastname='Admin',
432 name='RhodeCode', lastname='Admin',
433 active=True, admin=admin)
433 active=True, admin=admin)
434
434
435 def create_default_user(self):
435 def create_default_user(self):
436 log.info('creating default user')
436 log.info('creating default user')
437 # create default user for handling default permissions.
437 # create default user for handling default permissions.
438 UserModel().create_or_update(username='default',
438 UserModel().create_or_update(username='default',
439 password=str(uuid.uuid1())[:8],
439 password=str(uuid.uuid1())[:8],
440 email='anonymous@rhodecode.org',
440 email='anonymous@rhodecode.org',
441 name='Anonymous', lastname='User')
441 name='Anonymous', lastname='User')
442
442
443 def create_permissions(self):
443 def create_permissions(self):
444 # module.(access|create|change|delete)_[name]
444 # module.(access|create|change|delete)_[name]
445 # module.(read|write|owner)
445 # module.(none|read|write|admin)
446 perms = [('repository.none', 'Repository no access'),
446 perms = [
447 ('repository.read', 'Repository read access'),
447 ('repository.none', 'Repository no access'),
448 ('repository.write', 'Repository write access'),
448 ('repository.read', 'Repository read access'),
449 ('repository.admin', 'Repository admin access'),
449 ('repository.write', 'Repository write access'),
450 ('hg.admin', 'Hg Administrator'),
450 ('repository.admin', 'Repository admin access'),
451 ('hg.create.repository', 'Repository create'),
452 ('hg.create.none', 'Repository creation disabled'),
453 ('hg.register.none', 'Register disabled'),
454 ('hg.register.manual_activate', 'Register new user with '
455 'RhodeCode without manual'
456 'activation'),
457
451
458 ('hg.register.auto_activate', 'Register new user with '
452 ('group.none', 'Repositories Group no access'),
459 'RhodeCode without auto '
453 ('group.read', 'Repositories Group read access'),
460 'activation'),
454 ('group.write', 'Repositories Group write access'),
461 ]
455 ('group.admin', 'Repositories Group admin access'),
456
457 ('hg.admin', 'Hg Administrator'),
458 ('hg.create.repository', 'Repository create'),
459 ('hg.create.none', 'Repository creation disabled'),
460 ('hg.register.none', 'Register disabled'),
461 ('hg.register.manual_activate', 'Register new user with RhodeCode '
462 'without manual activation'),
463
464 ('hg.register.auto_activate', 'Register new user with RhodeCode '
465 'without auto activation'),
466 ]
462
467
463 for p in perms:
468 for p in perms:
464 new_perm = Permission()
469 new_perm = Permission()
465 new_perm.permission_name = p[0]
470 new_perm.permission_name = p[0]
466 new_perm.permission_longname = p[1]
471 new_perm.permission_longname = p[1]
467 self.sa.add(new_perm)
472 self.sa.add(new_perm)
468
473
469 def populate_default_permissions(self):
474 def populate_default_permissions(self):
470 log.info('creating default user permissions')
475 log.info('creating default user permissions')
471
476
472 default_user = self.sa.query(User)\
477 default_user = self.sa.query(User)\
473 .filter(User.username == 'default').scalar()
478 .filter(User.username == 'default').scalar()
474
479
475 reg_perm = UserToPerm()
480 reg_perm = UserToPerm()
476 reg_perm.user = default_user
481 reg_perm.user = default_user
477 reg_perm.permission = self.sa.query(Permission)\
482 reg_perm.permission = self.sa.query(Permission)\
478 .filter(Permission.permission_name == 'hg.register.manual_activate')\
483 .filter(Permission.permission_name == 'hg.register.manual_activate')\
479 .scalar()
484 .scalar()
480
485
481 create_repo_perm = UserToPerm()
486 create_repo_perm = UserToPerm()
482 create_repo_perm.user = default_user
487 create_repo_perm.user = default_user
483 create_repo_perm.permission = self.sa.query(Permission)\
488 create_repo_perm.permission = self.sa.query(Permission)\
484 .filter(Permission.permission_name == 'hg.create.repository')\
489 .filter(Permission.permission_name == 'hg.create.repository')\
485 .scalar()
490 .scalar()
486
491
487 default_repo_perm = UserToPerm()
492 default_repo_perm = UserToPerm()
488 default_repo_perm.user = default_user
493 default_repo_perm.user = default_user
489 default_repo_perm.permission = self.sa.query(Permission)\
494 default_repo_perm.permission = self.sa.query(Permission)\
490 .filter(Permission.permission_name == 'repository.read')\
495 .filter(Permission.permission_name == 'repository.read')\
491 .scalar()
496 .scalar()
492
497
493 self.sa.add(reg_perm)
498 self.sa.add(reg_perm)
494 self.sa.add(create_repo_perm)
499 self.sa.add(create_repo_perm)
495 self.sa.add(default_repo_perm)
500 self.sa.add(default_repo_perm)
@@ -1,155 +1,155 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.hooks
3 rhodecode.lib.hooks
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Hooks runned by rhodecode
6 Hooks runned by rhodecode
7
7
8 :created_on: Aug 6, 2010
8 :created_on: Aug 6, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import sys
26 import sys
27
27
28 from mercurial.scmutil import revrange
28 from mercurial.scmutil import revrange
29 from mercurial.node import nullrev
29 from mercurial.node import nullrev
30
30
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.utils import action_logger
32 from rhodecode.lib.utils import action_logger
33
33
34
34
35 def repo_size(ui, repo, hooktype=None, **kwargs):
35 def repo_size(ui, repo, hooktype=None, **kwargs):
36 """
36 """
37 Presents size of repository after push
37 Presents size of repository after push
38
38
39 :param ui:
39 :param ui:
40 :param repo:
40 :param repo:
41 :param hooktype:
41 :param hooktype:
42 """
42 """
43
43
44 size_hg, size_root = 0, 0
44 size_hg, size_root = 0, 0
45 for path, dirs, files in os.walk(repo.root):
45 for path, dirs, files in os.walk(repo.root):
46 if path.find('.hg') != -1:
46 if path.find('.hg') != -1:
47 for f in files:
47 for f in files:
48 try:
48 try:
49 size_hg += os.path.getsize(os.path.join(path, f))
49 size_hg += os.path.getsize(os.path.join(path, f))
50 except OSError:
50 except OSError:
51 pass
51 pass
52 else:
52 else:
53 for f in files:
53 for f in files:
54 try:
54 try:
55 size_root += os.path.getsize(os.path.join(path, f))
55 size_root += os.path.getsize(os.path.join(path, f))
56 except OSError:
56 except OSError:
57 pass
57 pass
58
58
59 size_hg_f = h.format_byte_size(size_hg)
59 size_hg_f = h.format_byte_size(size_hg)
60 size_root_f = h.format_byte_size(size_root)
60 size_root_f = h.format_byte_size(size_root)
61 size_total_f = h.format_byte_size(size_root + size_hg)
61 size_total_f = h.format_byte_size(size_root + size_hg)
62
62
63 last_cs = repo[len(repo) - 1]
63 last_cs = repo[len(repo) - 1]
64
64
65 msg = ('Repository size .hg:%s repo:%s total:%s\n'
65 msg = ('Repository size .hg:%s repo:%s total:%s\n'
66 'Last revision is now r%s:%s\n') % (
66 'Last revision is now r%s:%s\n') % (
67 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
67 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
68 )
68 )
69
69
70 sys.stdout.write(msg)
70 sys.stdout.write(msg)
71
71
72
72
73 def log_pull_action(ui, repo, **kwargs):
73 def log_pull_action(ui, repo, **kwargs):
74 """
74 """
75 Logs user last pull action
75 Logs user last pull action
76
76
77 :param ui:
77 :param ui:
78 :param repo:
78 :param repo:
79 """
79 """
80
80
81 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
81 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
82 username = extra_params['username']
82 username = extra_params['username']
83 repository = extra_params['repository']
83 repository = extra_params['repository']
84 action = 'pull'
84 action = 'pull'
85
85
86 action_logger(username, action, repository, extra_params['ip'],
86 action_logger(username, action, repository, extra_params['ip'],
87 commit=True)
87 commit=True)
88
88
89 return 0
89 return 0
90
90
91
91
92 def log_push_action(ui, repo, **kwargs):
92 def log_push_action(ui, repo, **kwargs):
93 """
93 """
94 Maps user last push action to new changeset id, from mercurial
94 Maps user last push action to new changeset id, from mercurial
95
95
96 :param ui:
96 :param ui:
97 :param repo:
97 :param repo:
98 """
98 """
99
99
100 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
100 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
101 username = extra_params['username']
101 username = extra_params['username']
102 repository = extra_params['repository']
102 repository = extra_params['repository']
103 action = extra_params['action'] + ':%s'
103 action = extra_params['action'] + ':%s'
104 node = kwargs['node']
104 node = kwargs['node']
105
105
106 def get_revs(repo, rev_opt):
106 def get_revs(repo, rev_opt):
107 if rev_opt:
107 if rev_opt:
108 revs = revrange(repo, rev_opt)
108 revs = revrange(repo, rev_opt)
109
109
110 if len(revs) == 0:
110 if len(revs) == 0:
111 return (nullrev, nullrev)
111 return (nullrev, nullrev)
112 return (max(revs), min(revs))
112 return (max(revs), min(revs))
113 else:
113 else:
114 return (len(repo) - 1, 0)
114 return (len(repo) - 1, 0)
115
115
116 stop, start = get_revs(repo, [node + ':'])
116 stop, start = get_revs(repo, [node + ':'])
117
117
118 revs = (str(repo[r]) for r in xrange(start, stop + 1))
118 revs = (str(repo[r]) for r in xrange(start, stop + 1))
119
119
120 action = action % ','.join(revs)
120 action = action % ','.join(revs)
121
121
122 action_logger(username, action, repository, extra_params['ip'],
122 action_logger(username, action, repository, extra_params['ip'],
123 commit=True)
123 commit=True)
124
124
125 return 0
125 return 0
126
126
127
127
128 def log_create_repository(repository_dict, created_by, **kwargs):
128 def log_create_repository(repository_dict, created_by, **kwargs):
129 """
129 """
130 Post create repository Hook. This is a dummy function for admins to re-use
130 Post create repository Hook. This is a dummy function for admins to re-use
131 if needed
131 if needed
132
132
133 :param repository: dict dump of repository object
133 :param repository: dict dump of repository object
134 :param created_by: username who created repository
134 :param created_by: username who created repository
135 :param created_date: date of creation
135 :param created_date: date of creation
136
136
137 available keys of repository_dict:
137 available keys of repository_dict:
138
138
139 'repo_type',
139 'repo_type',
140 'description',
140 'description',
141 'private',
141 'private',
142 'created_on',
142 'created_on',
143 'enable_downloads',
143 'enable_downloads',
144 'repo_id',
144 'repo_id',
145 'user_id',
145 'user_id',
146 'enable_statistics',
146 'enable_statistics',
147 'clone_uri',
147 'clone_uri',
148 'fork_id',
148 'fork_id',
149 'group_id',
149 'group_id',
150 'repo_name'
150 'repo_name'
151
151
152 """
152 """
153
153
154
154
155 return 0 No newline at end of file
155 return 0
@@ -1,599 +1,616 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import paste
30 import paste
31 import beaker
31 import beaker
32 import tarfile
32 import tarfile
33 import shutil
33 import shutil
34 from os.path import abspath
34 from os.path import abspath
35 from os.path import dirname as dn, join as jn
35 from os.path import dirname as dn, join as jn
36
36
37 from paste.script.command import Command, BadCommand
37 from paste.script.command import Command, BadCommand
38
38
39 from mercurial import ui, config
39 from mercurial import ui, config
40
40
41 from webhelpers.text import collapse, remove_formatting, strip_tags
41 from webhelpers.text import collapse, remove_formatting, strip_tags
42
42
43 from vcs import get_backend
43 from vcs import get_backend
44 from vcs.backends.base import BaseChangeset
44 from vcs.backends.base import BaseChangeset
45 from vcs.utils.lazy import LazyProperty
45 from vcs.utils.lazy import LazyProperty
46 from vcs.utils.helpers import get_scm
46 from vcs.utils.helpers import get_scm
47 from vcs.exceptions import VCSError
47 from vcs.exceptions import VCSError
48
48
49 from rhodecode.lib.caching_query import FromCache
49 from rhodecode.lib.caching_query import FromCache
50
50
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
52 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 UserLog, RepoGroup, RhodeCodeSetting
53 UserLog, RepoGroup, RhodeCodeSetting
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55 from rhodecode.model.repos_group import ReposGroupModel
55
56
56 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
57
58
58
59
59 def recursive_replace(str_, replace=' '):
60 def recursive_replace(str_, replace=' '):
60 """Recursive replace of given sign to just one instance
61 """Recursive replace of given sign to just one instance
61
62
62 :param str_: given string
63 :param str_: given string
63 :param replace: char to find and replace multiple instances
64 :param replace: char to find and replace multiple instances
64
65
65 Examples::
66 Examples::
66 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
67 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
67 'Mighty-Mighty-Bo-sstones'
68 'Mighty-Mighty-Bo-sstones'
68 """
69 """
69
70
70 if str_.find(replace * 2) == -1:
71 if str_.find(replace * 2) == -1:
71 return str_
72 return str_
72 else:
73 else:
73 str_ = str_.replace(replace * 2, replace)
74 str_ = str_.replace(replace * 2, replace)
74 return recursive_replace(str_, replace)
75 return recursive_replace(str_, replace)
75
76
76
77
77 def repo_name_slug(value):
78 def repo_name_slug(value):
78 """Return slug of name of repository
79 """Return slug of name of repository
79 This function is called on each creation/modification
80 This function is called on each creation/modification
80 of repository to prevent bad names in repo
81 of repository to prevent bad names in repo
81 """
82 """
82
83
83 slug = remove_formatting(value)
84 slug = remove_formatting(value)
84 slug = strip_tags(slug)
85 slug = strip_tags(slug)
85
86
86 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
87 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
87 slug = slug.replace(c, '-')
88 slug = slug.replace(c, '-')
88 slug = recursive_replace(slug, '-')
89 slug = recursive_replace(slug, '-')
89 slug = collapse(slug, '-')
90 slug = collapse(slug, '-')
90 return slug
91 return slug
91
92
92
93
93 def get_repo_slug(request):
94 def get_repo_slug(request):
94 return request.environ['pylons.routes_dict'].get('repo_name')
95 return request.environ['pylons.routes_dict'].get('repo_name')
95
96
96
97
98 def get_repos_group_slug(request):
99 return request.environ['pylons.routes_dict'].get('group_name')
100
101
97 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
102 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
98 """
103 """
99 Action logger for various actions made by users
104 Action logger for various actions made by users
100
105
101 :param user: user that made this action, can be a unique username string or
106 :param user: user that made this action, can be a unique username string or
102 object containing user_id attribute
107 object containing user_id attribute
103 :param action: action to log, should be on of predefined unique actions for
108 :param action: action to log, should be on of predefined unique actions for
104 easy translations
109 easy translations
105 :param repo: string name of repository or object containing repo_id,
110 :param repo: string name of repository or object containing repo_id,
106 that action was made on
111 that action was made on
107 :param ipaddr: optional ip address from what the action was made
112 :param ipaddr: optional ip address from what the action was made
108 :param sa: optional sqlalchemy session
113 :param sa: optional sqlalchemy session
109
114
110 """
115 """
111
116
112 if not sa:
117 if not sa:
113 sa = meta.Session
118 sa = meta.Session
114
119
115 try:
120 try:
116 if hasattr(user, 'user_id'):
121 if hasattr(user, 'user_id'):
117 user_obj = user
122 user_obj = user
118 elif isinstance(user, basestring):
123 elif isinstance(user, basestring):
119 user_obj = User.get_by_username(user)
124 user_obj = User.get_by_username(user)
120 else:
125 else:
121 raise Exception('You have to provide user object or username')
126 raise Exception('You have to provide user object or username')
122
127
123 if hasattr(repo, 'repo_id'):
128 if hasattr(repo, 'repo_id'):
124 repo_obj = Repository.get(repo.repo_id)
129 repo_obj = Repository.get(repo.repo_id)
125 repo_name = repo_obj.repo_name
130 repo_name = repo_obj.repo_name
126 elif isinstance(repo, basestring):
131 elif isinstance(repo, basestring):
127 repo_name = repo.lstrip('/')
132 repo_name = repo.lstrip('/')
128 repo_obj = Repository.get_by_repo_name(repo_name)
133 repo_obj = Repository.get_by_repo_name(repo_name)
129 else:
134 else:
130 raise Exception('You have to provide repository to action logger')
135 raise Exception('You have to provide repository to action logger')
131
136
132 user_log = UserLog()
137 user_log = UserLog()
133 user_log.user_id = user_obj.user_id
138 user_log.user_id = user_obj.user_id
134 user_log.action = action
139 user_log.action = action
135
140
136 user_log.repository_id = repo_obj.repo_id
141 user_log.repository_id = repo_obj.repo_id
137 user_log.repository_name = repo_name
142 user_log.repository_name = repo_name
138
143
139 user_log.action_date = datetime.datetime.now()
144 user_log.action_date = datetime.datetime.now()
140 user_log.user_ip = ipaddr
145 user_log.user_ip = ipaddr
141 sa.add(user_log)
146 sa.add(user_log)
142
147
143 log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
148 log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
144 if commit:
149 if commit:
145 sa.commit()
150 sa.commit()
146 except:
151 except:
147 log.error(traceback.format_exc())
152 log.error(traceback.format_exc())
148 raise
153 raise
149
154
150
155
151 def get_repos(path, recursive=False):
156 def get_repos(path, recursive=False):
152 """
157 """
153 Scans given path for repos and return (name,(type,path)) tuple
158 Scans given path for repos and return (name,(type,path)) tuple
154
159
155 :param path: path to scan for repositories
160 :param path: path to scan for repositories
156 :param recursive: recursive search and return names with subdirs in front
161 :param recursive: recursive search and return names with subdirs in front
157 """
162 """
158
163
159 # remove ending slash for better results
164 # remove ending slash for better results
160 path = path.rstrip(os.sep)
165 path = path.rstrip(os.sep)
161
166
162 def _get_repos(p):
167 def _get_repos(p):
163 if not os.access(p, os.W_OK):
168 if not os.access(p, os.W_OK):
164 return
169 return
165 for dirpath in os.listdir(p):
170 for dirpath in os.listdir(p):
166 if os.path.isfile(os.path.join(p, dirpath)):
171 if os.path.isfile(os.path.join(p, dirpath)):
167 continue
172 continue
168 cur_path = os.path.join(p, dirpath)
173 cur_path = os.path.join(p, dirpath)
169 try:
174 try:
170 scm_info = get_scm(cur_path)
175 scm_info = get_scm(cur_path)
171 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
176 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
172 except VCSError:
177 except VCSError:
173 if not recursive:
178 if not recursive:
174 continue
179 continue
175 #check if this dir containts other repos for recursive scan
180 #check if this dir containts other repos for recursive scan
176 rec_path = os.path.join(p, dirpath)
181 rec_path = os.path.join(p, dirpath)
177 if os.path.isdir(rec_path):
182 if os.path.isdir(rec_path):
178 for inner_scm in _get_repos(rec_path):
183 for inner_scm in _get_repos(rec_path):
179 yield inner_scm
184 yield inner_scm
180
185
181 return _get_repos(path)
186 return _get_repos(path)
182
187
183
188
184 def is_valid_repo(repo_name, base_path):
189 def is_valid_repo(repo_name, base_path):
185 """
190 """
186 Returns True if given path is a valid repository False otherwise
191 Returns True if given path is a valid repository False otherwise
187 :param repo_name:
192 :param repo_name:
188 :param base_path:
193 :param base_path:
189
194
190 :return True: if given path is a valid repository
195 :return True: if given path is a valid repository
191 """
196 """
192 full_path = os.path.join(base_path, repo_name)
197 full_path = os.path.join(base_path, repo_name)
193
198
194 try:
199 try:
195 get_scm(full_path)
200 get_scm(full_path)
196 return True
201 return True
197 except VCSError:
202 except VCSError:
198 return False
203 return False
199
204
205
200 def is_valid_repos_group(repos_group_name, base_path):
206 def is_valid_repos_group(repos_group_name, base_path):
201 """
207 """
202 Returns True if given path is a repos group False otherwise
208 Returns True if given path is a repos group False otherwise
203
209
204 :param repo_name:
210 :param repo_name:
205 :param base_path:
211 :param base_path:
206 """
212 """
207 full_path = os.path.join(base_path, repos_group_name)
213 full_path = os.path.join(base_path, repos_group_name)
208
214
209 # check if it's not a repo
215 # check if it's not a repo
210 if is_valid_repo(repos_group_name, base_path):
216 if is_valid_repo(repos_group_name, base_path):
211 return False
217 return False
212
218
213 # check if it's a valid path
219 # check if it's a valid path
214 if os.path.isdir(full_path):
220 if os.path.isdir(full_path):
215 return True
221 return True
216
222
217 return False
223 return False
218
224
225
219 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
226 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
220 while True:
227 while True:
221 ok = raw_input(prompt)
228 ok = raw_input(prompt)
222 if ok in ('y', 'ye', 'yes'):
229 if ok in ('y', 'ye', 'yes'):
223 return True
230 return True
224 if ok in ('n', 'no', 'nop', 'nope'):
231 if ok in ('n', 'no', 'nop', 'nope'):
225 return False
232 return False
226 retries = retries - 1
233 retries = retries - 1
227 if retries < 0:
234 if retries < 0:
228 raise IOError
235 raise IOError
229 print complaint
236 print complaint
230
237
231 #propagated from mercurial documentation
238 #propagated from mercurial documentation
232 ui_sections = ['alias', 'auth',
239 ui_sections = ['alias', 'auth',
233 'decode/encode', 'defaults',
240 'decode/encode', 'defaults',
234 'diff', 'email',
241 'diff', 'email',
235 'extensions', 'format',
242 'extensions', 'format',
236 'merge-patterns', 'merge-tools',
243 'merge-patterns', 'merge-tools',
237 'hooks', 'http_proxy',
244 'hooks', 'http_proxy',
238 'smtp', 'patch',
245 'smtp', 'patch',
239 'paths', 'profiling',
246 'paths', 'profiling',
240 'server', 'trusted',
247 'server', 'trusted',
241 'ui', 'web', ]
248 'ui', 'web', ]
242
249
243
250
244 def make_ui(read_from='file', path=None, checkpaths=True):
251 def make_ui(read_from='file', path=None, checkpaths=True):
245 """A function that will read python rc files or database
252 """A function that will read python rc files or database
246 and make an mercurial ui object from read options
253 and make an mercurial ui object from read options
247
254
248 :param path: path to mercurial config file
255 :param path: path to mercurial config file
249 :param checkpaths: check the path
256 :param checkpaths: check the path
250 :param read_from: read from 'file' or 'db'
257 :param read_from: read from 'file' or 'db'
251 """
258 """
252
259
253 baseui = ui.ui()
260 baseui = ui.ui()
254
261
255 #clean the baseui object
262 #clean the baseui object
256 baseui._ocfg = config.config()
263 baseui._ocfg = config.config()
257 baseui._ucfg = config.config()
264 baseui._ucfg = config.config()
258 baseui._tcfg = config.config()
265 baseui._tcfg = config.config()
259
266
260 if read_from == 'file':
267 if read_from == 'file':
261 if not os.path.isfile(path):
268 if not os.path.isfile(path):
262 log.warning('Unable to read config file %s' % path)
269 log.warning('Unable to read config file %s' % path)
263 return False
270 return False
264 log.debug('reading hgrc from %s' % path)
271 log.debug('reading hgrc from %s' % path)
265 cfg = config.config()
272 cfg = config.config()
266 cfg.read(path)
273 cfg.read(path)
267 for section in ui_sections:
274 for section in ui_sections:
268 for k, v in cfg.items(section):
275 for k, v in cfg.items(section):
269 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
276 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
270 baseui.setconfig(section, k, v)
277 baseui.setconfig(section, k, v)
271
278
272 elif read_from == 'db':
279 elif read_from == 'db':
273 sa = meta.Session
280 sa = meta.Session
274 ret = sa.query(RhodeCodeUi)\
281 ret = sa.query(RhodeCodeUi)\
275 .options(FromCache("sql_cache_short",
282 .options(FromCache("sql_cache_short",
276 "get_hg_ui_settings")).all()
283 "get_hg_ui_settings")).all()
277
284
278 hg_ui = ret
285 hg_ui = ret
279 for ui_ in hg_ui:
286 for ui_ in hg_ui:
280 if ui_.ui_active:
287 if ui_.ui_active:
281 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
288 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
282 ui_.ui_key, ui_.ui_value)
289 ui_.ui_key, ui_.ui_value)
283 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
290 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
284
291
285 meta.Session.remove()
292 meta.Session.remove()
286 return baseui
293 return baseui
287
294
288
295
289 def set_rhodecode_config(config):
296 def set_rhodecode_config(config):
290 """
297 """
291 Updates pylons config with new settings from database
298 Updates pylons config with new settings from database
292
299
293 :param config:
300 :param config:
294 """
301 """
295 hgsettings = RhodeCodeSetting.get_app_settings()
302 hgsettings = RhodeCodeSetting.get_app_settings()
296
303
297 for k, v in hgsettings.items():
304 for k, v in hgsettings.items():
298 config[k] = v
305 config[k] = v
299
306
300
307
301 def invalidate_cache(cache_key, *args):
308 def invalidate_cache(cache_key, *args):
302 """
309 """
303 Puts cache invalidation task into db for
310 Puts cache invalidation task into db for
304 further global cache invalidation
311 further global cache invalidation
305 """
312 """
306
313
307 from rhodecode.model.scm import ScmModel
314 from rhodecode.model.scm import ScmModel
308
315
309 if cache_key.startswith('get_repo_cached_'):
316 if cache_key.startswith('get_repo_cached_'):
310 name = cache_key.split('get_repo_cached_')[-1]
317 name = cache_key.split('get_repo_cached_')[-1]
311 ScmModel().mark_for_invalidation(name)
318 ScmModel().mark_for_invalidation(name)
312
319
313
320
314 class EmptyChangeset(BaseChangeset):
321 class EmptyChangeset(BaseChangeset):
315 """
322 """
316 An dummy empty changeset. It's possible to pass hash when creating
323 An dummy empty changeset. It's possible to pass hash when creating
317 an EmptyChangeset
324 an EmptyChangeset
318 """
325 """
319
326
320 def __init__(self, cs='0' * 40, repo=None, requested_revision=None, alias=None):
327 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
328 alias=None):
321 self._empty_cs = cs
329 self._empty_cs = cs
322 self.revision = -1
330 self.revision = -1
323 self.message = ''
331 self.message = ''
324 self.author = ''
332 self.author = ''
325 self.date = ''
333 self.date = ''
326 self.repository = repo
334 self.repository = repo
327 self.requested_revision = requested_revision
335 self.requested_revision = requested_revision
328 self.alias = alias
336 self.alias = alias
329
337
330 @LazyProperty
338 @LazyProperty
331 def raw_id(self):
339 def raw_id(self):
332 """
340 """
333 Returns raw string identifying this changeset, useful for web
341 Returns raw string identifying this changeset, useful for web
334 representation.
342 representation.
335 """
343 """
336
344
337 return self._empty_cs
345 return self._empty_cs
338
346
339 @LazyProperty
347 @LazyProperty
340 def branch(self):
348 def branch(self):
341 return get_backend(self.alias).DEFAULT_BRANCH_NAME
349 return get_backend(self.alias).DEFAULT_BRANCH_NAME
342
350
343 @LazyProperty
351 @LazyProperty
344 def short_id(self):
352 def short_id(self):
345 return self.raw_id[:12]
353 return self.raw_id[:12]
346
354
347 def get_file_changeset(self, path):
355 def get_file_changeset(self, path):
348 return self
356 return self
349
357
350 def get_file_content(self, path):
358 def get_file_content(self, path):
351 return u''
359 return u''
352
360
353 def get_file_size(self, path):
361 def get_file_size(self, path):
354 return 0
362 return 0
355
363
356
364
357 def map_groups(groups):
365 def map_groups(groups):
358 """
366 """
359 Checks for groups existence, and creates groups structures.
367 Checks for groups existence, and creates groups structures.
360 It returns last group in structure
368 It returns last group in structure
361
369
362 :param groups: list of groups structure
370 :param groups: list of groups structure
363 """
371 """
364 sa = meta.Session
372 sa = meta.Session
365
373
366 parent = None
374 parent = None
367 group = None
375 group = None
368
376
369 # last element is repo in nested groups structure
377 # last element is repo in nested groups structure
370 groups = groups[:-1]
378 groups = groups[:-1]
371
379 rgm = ReposGroupModel(sa)
372 for lvl, group_name in enumerate(groups):
380 for lvl, group_name in enumerate(groups):
381 log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
373 group_name = '/'.join(groups[:lvl] + [group_name])
382 group_name = '/'.join(groups[:lvl] + [group_name])
374 group = sa.query(RepoGroup).filter(RepoGroup.group_name == group_name).scalar()
383 group = RepoGroup.get_by_group_name(group_name)
384 desc = '%s group' % group_name
385
386 # # WTF that doesn't work !?
387 # if group is None:
388 # group = rgm.create(group_name, desc, parent, just_db=True)
389 # sa.commit()
375
390
376 if group is None:
391 if group is None:
377 group = RepoGroup(group_name, parent)
392 group = RepoGroup(group_name, parent)
393 group.group_description = desc
378 sa.add(group)
394 sa.add(group)
395 rgm._create_default_perms(group)
379 sa.commit()
396 sa.commit()
380 parent = group
397 parent = group
381 return group
398 return group
382
399
383
400
384 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
401 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
385 """
402 """
386 maps all repos given in initial_repo_list, non existing repositories
403 maps all repos given in initial_repo_list, non existing repositories
387 are created, if remove_obsolete is True it also check for db entries
404 are created, if remove_obsolete is True it also check for db entries
388 that are not in initial_repo_list and removes them.
405 that are not in initial_repo_list and removes them.
389
406
390 :param initial_repo_list: list of repositories found by scanning methods
407 :param initial_repo_list: list of repositories found by scanning methods
391 :param remove_obsolete: check for obsolete entries in database
408 :param remove_obsolete: check for obsolete entries in database
392 """
409 """
393 from rhodecode.model.repo import RepoModel
410 from rhodecode.model.repo import RepoModel
394 sa = meta.Session
411 sa = meta.Session
395 rm = RepoModel()
412 rm = RepoModel()
396 user = sa.query(User).filter(User.admin == True).first()
413 user = sa.query(User).filter(User.admin == True).first()
397 if user is None:
414 if user is None:
398 raise Exception('Missing administrative account !')
415 raise Exception('Missing administrative account !')
399 added = []
416 added = []
400
417
401 for name, repo in initial_repo_list.items():
418 for name, repo in initial_repo_list.items():
402 group = map_groups(name.split(Repository.url_sep()))
419 group = map_groups(name.split(Repository.url_sep()))
403 if not rm.get_by_repo_name(name, cache=False):
420 if not rm.get_by_repo_name(name, cache=False):
404 log.info('repository %s not found creating default' % name)
421 log.info('repository %s not found creating default' % name)
405 added.append(name)
422 added.append(name)
406 form_data = {
423 form_data = {
407 'repo_name': name,
424 'repo_name': name,
408 'repo_name_full': name,
425 'repo_name_full': name,
409 'repo_type': repo.alias,
426 'repo_type': repo.alias,
410 'description': repo.description \
427 'description': repo.description \
411 if repo.description != 'unknown' else \
428 if repo.description != 'unknown' else '%s repository' % name,
412 '%s repository' % name,
429 'private': False,
413 'private': False,
430 'group_id': getattr(group, 'group_id', None)
414 'group_id': getattr(group, 'group_id', None)
431 }
415 }
416 rm.create(form_data, user, just_db=True)
432 rm.create(form_data, user, just_db=True)
417 sa.commit()
433 sa.commit()
418 removed = []
434 removed = []
419 if remove_obsolete:
435 if remove_obsolete:
420 #remove from database those repositories that are not in the filesystem
436 #remove from database those repositories that are not in the filesystem
421 for repo in sa.query(Repository).all():
437 for repo in sa.query(Repository).all():
422 if repo.repo_name not in initial_repo_list.keys():
438 if repo.repo_name not in initial_repo_list.keys():
423 removed.append(repo.repo_name)
439 removed.append(repo.repo_name)
424 sa.delete(repo)
440 sa.delete(repo)
425 sa.commit()
441 sa.commit()
426
442
427 return added, removed
443 return added, removed
428
444
445
429 # set cache regions for beaker so celery can utilise it
446 # set cache regions for beaker so celery can utilise it
430 def add_cache(settings):
447 def add_cache(settings):
431 cache_settings = {'regions': None}
448 cache_settings = {'regions': None}
432 for key in settings.keys():
449 for key in settings.keys():
433 for prefix in ['beaker.cache.', 'cache.']:
450 for prefix in ['beaker.cache.', 'cache.']:
434 if key.startswith(prefix):
451 if key.startswith(prefix):
435 name = key.split(prefix)[1].strip()
452 name = key.split(prefix)[1].strip()
436 cache_settings[name] = settings[key].strip()
453 cache_settings[name] = settings[key].strip()
437 if cache_settings['regions']:
454 if cache_settings['regions']:
438 for region in cache_settings['regions'].split(','):
455 for region in cache_settings['regions'].split(','):
439 region = region.strip()
456 region = region.strip()
440 region_settings = {}
457 region_settings = {}
441 for key, value in cache_settings.items():
458 for key, value in cache_settings.items():
442 if key.startswith(region):
459 if key.startswith(region):
443 region_settings[key.split('.')[1]] = value
460 region_settings[key.split('.')[1]] = value
444 region_settings['expire'] = int(region_settings.get('expire',
461 region_settings['expire'] = int(region_settings.get('expire',
445 60))
462 60))
446 region_settings.setdefault('lock_dir',
463 region_settings.setdefault('lock_dir',
447 cache_settings.get('lock_dir'))
464 cache_settings.get('lock_dir'))
448 region_settings.setdefault('data_dir',
465 region_settings.setdefault('data_dir',
449 cache_settings.get('data_dir'))
466 cache_settings.get('data_dir'))
450
467
451 if 'type' not in region_settings:
468 if 'type' not in region_settings:
452 region_settings['type'] = cache_settings.get('type',
469 region_settings['type'] = cache_settings.get('type',
453 'memory')
470 'memory')
454 beaker.cache.cache_regions[region] = region_settings
471 beaker.cache.cache_regions[region] = region_settings
455
472
456
473
457 #==============================================================================
474 #==============================================================================
458 # TEST FUNCTIONS AND CREATORS
475 # TEST FUNCTIONS AND CREATORS
459 #==============================================================================
476 #==============================================================================
460 def create_test_index(repo_location, config, full_index):
477 def create_test_index(repo_location, config, full_index):
461 """
478 """
462 Makes default test index
479 Makes default test index
463
480
464 :param config: test config
481 :param config: test config
465 :param full_index:
482 :param full_index:
466 """
483 """
467
484
468 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
485 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
469 from rhodecode.lib.pidlock import DaemonLock, LockHeld
486 from rhodecode.lib.pidlock import DaemonLock, LockHeld
470
487
471 repo_location = repo_location
488 repo_location = repo_location
472
489
473 index_location = os.path.join(config['app_conf']['index_dir'])
490 index_location = os.path.join(config['app_conf']['index_dir'])
474 if not os.path.exists(index_location):
491 if not os.path.exists(index_location):
475 os.makedirs(index_location)
492 os.makedirs(index_location)
476
493
477 try:
494 try:
478 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
495 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
479 WhooshIndexingDaemon(index_location=index_location,
496 WhooshIndexingDaemon(index_location=index_location,
480 repo_location=repo_location)\
497 repo_location=repo_location)\
481 .run(full_index=full_index)
498 .run(full_index=full_index)
482 l.release()
499 l.release()
483 except LockHeld:
500 except LockHeld:
484 pass
501 pass
485
502
486
503
487 def create_test_env(repos_test_path, config):
504 def create_test_env(repos_test_path, config):
488 """
505 """
489 Makes a fresh database and
506 Makes a fresh database and
490 install test repository into tmp dir
507 install test repository into tmp dir
491 """
508 """
492 from rhodecode.lib.db_manage import DbManage
509 from rhodecode.lib.db_manage import DbManage
493 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
510 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
494
511
495 # PART ONE create db
512 # PART ONE create db
496 dbconf = config['sqlalchemy.db1.url']
513 dbconf = config['sqlalchemy.db1.url']
497 log.debug('making test db %s' % dbconf)
514 log.debug('making test db %s' % dbconf)
498
515
499 # create test dir if it doesn't exist
516 # create test dir if it doesn't exist
500 if not os.path.isdir(repos_test_path):
517 if not os.path.isdir(repos_test_path):
501 log.debug('Creating testdir %s' % repos_test_path)
518 log.debug('Creating testdir %s' % repos_test_path)
502 os.makedirs(repos_test_path)
519 os.makedirs(repos_test_path)
503
520
504 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
521 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
505 tests=True)
522 tests=True)
506 dbmanage.create_tables(override=True)
523 dbmanage.create_tables(override=True)
507 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
524 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
508 dbmanage.create_default_user()
525 dbmanage.create_default_user()
509 dbmanage.admin_prompt()
526 dbmanage.admin_prompt()
510 dbmanage.create_permissions()
527 dbmanage.create_permissions()
511 dbmanage.populate_default_permissions()
528 dbmanage.populate_default_permissions()
512 Session.commit()
529 Session.commit()
513 # PART TWO make test repo
530 # PART TWO make test repo
514 log.debug('making test vcs repositories')
531 log.debug('making test vcs repositories')
515
532
516 idx_path = config['app_conf']['index_dir']
533 idx_path = config['app_conf']['index_dir']
517 data_path = config['app_conf']['cache_dir']
534 data_path = config['app_conf']['cache_dir']
518
535
519 #clean index and data
536 #clean index and data
520 if idx_path and os.path.exists(idx_path):
537 if idx_path and os.path.exists(idx_path):
521 log.debug('remove %s' % idx_path)
538 log.debug('remove %s' % idx_path)
522 shutil.rmtree(idx_path)
539 shutil.rmtree(idx_path)
523
540
524 if data_path and os.path.exists(data_path):
541 if data_path and os.path.exists(data_path):
525 log.debug('remove %s' % data_path)
542 log.debug('remove %s' % data_path)
526 shutil.rmtree(data_path)
543 shutil.rmtree(data_path)
527
544
528 #CREATE DEFAULT HG REPOSITORY
545 #CREATE DEFAULT HG REPOSITORY
529 cur_dir = dn(dn(abspath(__file__)))
546 cur_dir = dn(dn(abspath(__file__)))
530 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
547 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
531 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
548 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
532 tar.close()
549 tar.close()
533
550
534
551
535 #==============================================================================
552 #==============================================================================
536 # PASTER COMMANDS
553 # PASTER COMMANDS
537 #==============================================================================
554 #==============================================================================
538 class BasePasterCommand(Command):
555 class BasePasterCommand(Command):
539 """
556 """
540 Abstract Base Class for paster commands.
557 Abstract Base Class for paster commands.
541
558
542 The celery commands are somewhat aggressive about loading
559 The celery commands are somewhat aggressive about loading
543 celery.conf, and since our module sets the `CELERY_LOADER`
560 celery.conf, and since our module sets the `CELERY_LOADER`
544 environment variable to our loader, we have to bootstrap a bit and
561 environment variable to our loader, we have to bootstrap a bit and
545 make sure we've had a chance to load the pylons config off of the
562 make sure we've had a chance to load the pylons config off of the
546 command line, otherwise everything fails.
563 command line, otherwise everything fails.
547 """
564 """
548 min_args = 1
565 min_args = 1
549 min_args_error = "Please provide a paster config file as an argument."
566 min_args_error = "Please provide a paster config file as an argument."
550 takes_config_file = 1
567 takes_config_file = 1
551 requires_config_file = True
568 requires_config_file = True
552
569
553 def notify_msg(self, msg, log=False):
570 def notify_msg(self, msg, log=False):
554 """Make a notification to user, additionally if logger is passed
571 """Make a notification to user, additionally if logger is passed
555 it logs this action using given logger
572 it logs this action using given logger
556
573
557 :param msg: message that will be printed to user
574 :param msg: message that will be printed to user
558 :param log: logging instance, to use to additionally log this message
575 :param log: logging instance, to use to additionally log this message
559
576
560 """
577 """
561 if log and isinstance(log, logging):
578 if log and isinstance(log, logging):
562 log(msg)
579 log(msg)
563
580
564 def run(self, args):
581 def run(self, args):
565 """
582 """
566 Overrides Command.run
583 Overrides Command.run
567
584
568 Checks for a config file argument and loads it.
585 Checks for a config file argument and loads it.
569 """
586 """
570 if len(args) < self.min_args:
587 if len(args) < self.min_args:
571 raise BadCommand(
588 raise BadCommand(
572 self.min_args_error % {'min_args': self.min_args,
589 self.min_args_error % {'min_args': self.min_args,
573 'actual_args': len(args)})
590 'actual_args': len(args)})
574
591
575 # Decrement because we're going to lob off the first argument.
592 # Decrement because we're going to lob off the first argument.
576 # @@ This is hacky
593 # @@ This is hacky
577 self.min_args -= 1
594 self.min_args -= 1
578 self.bootstrap_config(args[0])
595 self.bootstrap_config(args[0])
579 self.update_parser()
596 self.update_parser()
580 return super(BasePasterCommand, self).run(args[1:])
597 return super(BasePasterCommand, self).run(args[1:])
581
598
582 def update_parser(self):
599 def update_parser(self):
583 """
600 """
584 Abstract method. Allows for the class's parser to be updated
601 Abstract method. Allows for the class's parser to be updated
585 before the superclass's `run` method is called. Necessary to
602 before the superclass's `run` method is called. Necessary to
586 allow options/arguments to be passed through to the underlying
603 allow options/arguments to be passed through to the underlying
587 celery command.
604 celery command.
588 """
605 """
589 raise NotImplementedError("Abstract Method.")
606 raise NotImplementedError("Abstract Method.")
590
607
591 def bootstrap_config(self, conf):
608 def bootstrap_config(self, conf):
592 """
609 """
593 Loads the pylons configuration.
610 Loads the pylons configuration.
594 """
611 """
595 from pylons import config as pylonsconfig
612 from pylons import config as pylonsconfig
596
613
597 path_to_ini_file = os.path.realpath(conf)
614 path_to_ini_file = os.path.realpath(conf)
598 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
615 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
599 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
616 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,92 +1,98 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.__init__
3 rhodecode.model.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 The application's model objects
6 The application's model objects
7
7
8 :created_on: Nov 25, 2010
8 :created_on: Nov 25, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12
12
13
13
14 :example:
14 :example:
15
15
16 .. code-block:: python
16 .. code-block:: python
17
17
18 from paste.deploy import appconfig
18 from paste.deploy import appconfig
19 from pylons import config
19 from pylons import config
20 from sqlalchemy import engine_from_config
20 from sqlalchemy import engine_from_config
21 from rhodecode.config.environment import load_environment
21 from rhodecode.config.environment import load_environment
22
22
23 conf = appconfig('config:development.ini', relative_to = './../../')
23 conf = appconfig('config:development.ini', relative_to = './../../')
24 load_environment(conf.global_conf, conf.local_conf)
24 load_environment(conf.global_conf, conf.local_conf)
25
25
26 engine = engine_from_config(config, 'sqlalchemy.')
26 engine = engine_from_config(config, 'sqlalchemy.')
27 init_model(engine)
27 init_model(engine)
28 # RUN YOUR CODE HERE
28 # RUN YOUR CODE HERE
29
29
30 """
30 """
31 # This program is free software: you can redistribute it and/or modify
31 # This program is free software: you can redistribute it and/or modify
32 # it under the terms of the GNU General Public License as published by
32 # it under the terms of the GNU General Public License as published by
33 # the Free Software Foundation, either version 3 of the License, or
33 # the Free Software Foundation, either version 3 of the License, or
34 # (at your option) any later version.
34 # (at your option) any later version.
35 #
35 #
36 # This program is distributed in the hope that it will be useful,
36 # This program is distributed in the hope that it will be useful,
37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 # GNU General Public License for more details.
39 # GNU General Public License for more details.
40 #
40 #
41 # You should have received a copy of the GNU General Public License
41 # You should have received a copy of the GNU General Public License
42 # along with this program. If not, see <http://www.gnu.org/licenses/>.
42 # along with this program. If not, see <http://www.gnu.org/licenses/>.
43
43
44 import logging
44 import logging
45
45
46 from rhodecode.model import meta
46 from rhodecode.model import meta
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 def init_model(engine):
51 def init_model(engine):
52 """
52 """
53 Initializes db session, bind the engine with the metadata,
53 Initializes db session, bind the engine with the metadata,
54 Call this before using any of the tables or classes in the model,
54 Call this before using any of the tables or classes in the model,
55 preferably once in application start
55 preferably once in application start
56
56
57 :param engine: engine to bind to
57 :param engine: engine to bind to
58 """
58 """
59 log.info("initializing db for %s" % engine)
59 log.info("initializing db for %s" % engine)
60 meta.Base.metadata.bind = engine
60 meta.Base.metadata.bind = engine
61
61
62
62
63 class BaseModel(object):
63 class BaseModel(object):
64 """
64 """
65 Base Model for all RhodeCode models, it adds sql alchemy session
65 Base Model for all RhodeCode models, it adds sql alchemy session
66 into instance of model
66 into instance of model
67
67
68 :param sa: If passed it reuses this session instead of creating a new one
68 :param sa: If passed it reuses this session instead of creating a new one
69 """
69 """
70
70
71 def __init__(self, sa=None):
71 def __init__(self, sa=None):
72 if sa is not None:
72 if sa is not None:
73 self.sa = sa
73 self.sa = sa
74 else:
74 else:
75 self.sa = meta.Session
75 self.sa = meta.Session
76
76
77 def _get_instance(self, cls, instance):
77 def _get_instance(self, cls, instance, callback=None):
78 """
78 """
79 Get's instance of given cls using some simple lookup mechanism
79 Get's instance of given cls using some simple lookup mechanism.
80
80
81 :param cls: class to fetch
81 :param cls: class to fetch
82 :param instance: int or Instance
82 :param instance: int or Instance
83 :param callback: callback to call if all lookups failed
83 """
84 """
84
85
85 if isinstance(instance, cls):
86 if isinstance(instance, cls):
86 return instance
87 return instance
87 elif isinstance(instance, int) or str(instance).isdigit():
88 elif isinstance(instance, int) or str(instance).isdigit():
88 return cls.get(instance)
89 return cls.get(instance)
89 else:
90 else:
90 if instance:
91 if instance:
91 raise Exception('given object must be int or Instance'
92 if callback is None:
92 ' of %s got %s' % (type(cls), type(instance)))
93 raise Exception(
94 'given object must be int or Instance of %s got %s, '
95 'no callback provided' % (cls, type(instance))
96 )
97 else:
98 return callback(instance)
@@ -1,1173 +1,1186 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 from collections import defaultdict
30 from collections import defaultdict
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.ext.hybrid import hybrid_property
33 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from beaker.cache import cache_region, region_invalidate
35 from beaker.cache import cache_region, region_invalidate
36
36
37 from vcs import get_backend
37 from vcs import get_backend
38 from vcs.utils.helpers import get_scm
38 from vcs.utils.helpers import get_scm
39 from vcs.exceptions import VCSError
39 from vcs.exceptions import VCSError
40 from vcs.utils.lazy import LazyProperty
40 from vcs.utils.lazy import LazyProperty
41
41
42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
43 from rhodecode.lib.exceptions import UsersGroupsAssignedException
43 from rhodecode.lib.exceptions import UsersGroupsAssignedException
44 from rhodecode.lib.compat import json
44 from rhodecode.lib.compat import json
45 from rhodecode.lib.caching_query import FromCache
45 from rhodecode.lib.caching_query import FromCache
46
46
47 from rhodecode.model.meta import Base, Session
47 from rhodecode.model.meta import Base, Session
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51 #==============================================================================
51 #==============================================================================
52 # BASE CLASSES
52 # BASE CLASSES
53 #==============================================================================
53 #==============================================================================
54
54
55
55
56 class ModelSerializer(json.JSONEncoder):
56 class ModelSerializer(json.JSONEncoder):
57 """
57 """
58 Simple Serializer for JSON,
58 Simple Serializer for JSON,
59
59
60 usage::
60 usage::
61
61
62 to make object customized for serialization implement a __json__
62 to make object customized for serialization implement a __json__
63 method that will return a dict for serialization into json
63 method that will return a dict for serialization into json
64
64
65 example::
65 example::
66
66
67 class Task(object):
67 class Task(object):
68
68
69 def __init__(self, name, value):
69 def __init__(self, name, value):
70 self.name = name
70 self.name = name
71 self.value = value
71 self.value = value
72
72
73 def __json__(self):
73 def __json__(self):
74 return dict(name=self.name,
74 return dict(name=self.name,
75 value=self.value)
75 value=self.value)
76
76
77 """
77 """
78
78
79 def default(self, obj):
79 def default(self, obj):
80
80
81 if hasattr(obj, '__json__'):
81 if hasattr(obj, '__json__'):
82 return obj.__json__()
82 return obj.__json__()
83 else:
83 else:
84 return json.JSONEncoder.default(self, obj)
84 return json.JSONEncoder.default(self, obj)
85
85
86
86
87 class BaseModel(object):
87 class BaseModel(object):
88 """
88 """
89 Base Model for all classess
89 Base Model for all classess
90 """
90 """
91
91
92 @classmethod
92 @classmethod
93 def _get_keys(cls):
93 def _get_keys(cls):
94 """return column names for this model """
94 """return column names for this model """
95 return class_mapper(cls).c.keys()
95 return class_mapper(cls).c.keys()
96
96
97 def get_dict(self):
97 def get_dict(self):
98 """
98 """
99 return dict with keys and values corresponding
99 return dict with keys and values corresponding
100 to this model data """
100 to this model data """
101
101
102 d = {}
102 d = {}
103 for k in self._get_keys():
103 for k in self._get_keys():
104 d[k] = getattr(self, k)
104 d[k] = getattr(self, k)
105
105
106 # also use __json__() if present to get additional fields
106 # also use __json__() if present to get additional fields
107 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
107 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
108 d[k] = val
108 d[k] = val
109 return d
109 return d
110
110
111 def get_appstruct(self):
111 def get_appstruct(self):
112 """return list with keys and values tupples corresponding
112 """return list with keys and values tupples corresponding
113 to this model data """
113 to this model data """
114
114
115 l = []
115 l = []
116 for k in self._get_keys():
116 for k in self._get_keys():
117 l.append((k, getattr(self, k),))
117 l.append((k, getattr(self, k),))
118 return l
118 return l
119
119
120 def populate_obj(self, populate_dict):
120 def populate_obj(self, populate_dict):
121 """populate model with data from given populate_dict"""
121 """populate model with data from given populate_dict"""
122
122
123 for k in self._get_keys():
123 for k in self._get_keys():
124 if k in populate_dict:
124 if k in populate_dict:
125 setattr(self, k, populate_dict[k])
125 setattr(self, k, populate_dict[k])
126
126
127 @classmethod
127 @classmethod
128 def query(cls):
128 def query(cls):
129 return Session.query(cls)
129 return Session.query(cls)
130
130
131 @classmethod
131 @classmethod
132 def get(cls, id_):
132 def get(cls, id_):
133 if id_:
133 if id_:
134 return cls.query().get(id_)
134 return cls.query().get(id_)
135
135
136 @classmethod
136 @classmethod
137 def getAll(cls):
137 def getAll(cls):
138 return cls.query().all()
138 return cls.query().all()
139
139
140 @classmethod
140 @classmethod
141 def delete(cls, id_):
141 def delete(cls, id_):
142 obj = cls.query().get(id_)
142 obj = cls.query().get(id_)
143 Session.delete(obj)
143 Session.delete(obj)
144
144
145
145
146 class RhodeCodeSetting(Base, BaseModel):
146 class RhodeCodeSetting(Base, BaseModel):
147 __tablename__ = 'rhodecode_settings'
147 __tablename__ = 'rhodecode_settings'
148 __table_args__ = (
148 __table_args__ = (
149 UniqueConstraint('app_settings_name'),
149 UniqueConstraint('app_settings_name'),
150 {'extend_existing': True}
150 {'extend_existing': True}
151 )
151 )
152 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
152 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
153 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
153 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
154 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
154 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
155
155
156 def __init__(self, k='', v=''):
156 def __init__(self, k='', v=''):
157 self.app_settings_name = k
157 self.app_settings_name = k
158 self.app_settings_value = v
158 self.app_settings_value = v
159
159
160 @validates('_app_settings_value')
160 @validates('_app_settings_value')
161 def validate_settings_value(self, key, val):
161 def validate_settings_value(self, key, val):
162 assert type(val) == unicode
162 assert type(val) == unicode
163 return val
163 return val
164
164
165 @hybrid_property
165 @hybrid_property
166 def app_settings_value(self):
166 def app_settings_value(self):
167 v = self._app_settings_value
167 v = self._app_settings_value
168 if v == 'ldap_active':
168 if v == 'ldap_active':
169 v = str2bool(v)
169 v = str2bool(v)
170 return v
170 return v
171
171
172 @app_settings_value.setter
172 @app_settings_value.setter
173 def app_settings_value(self, val):
173 def app_settings_value(self, val):
174 """
174 """
175 Setter that will always make sure we use unicode in app_settings_value
175 Setter that will always make sure we use unicode in app_settings_value
176
176
177 :param val:
177 :param val:
178 """
178 """
179 self._app_settings_value = safe_unicode(val)
179 self._app_settings_value = safe_unicode(val)
180
180
181 def __repr__(self):
181 def __repr__(self):
182 return "<%s('%s:%s')>" % (
182 return "<%s('%s:%s')>" % (
183 self.__class__.__name__,
183 self.__class__.__name__,
184 self.app_settings_name, self.app_settings_value
184 self.app_settings_name, self.app_settings_value
185 )
185 )
186
186
187 @classmethod
187 @classmethod
188 def get_by_name(cls, ldap_key):
188 def get_by_name(cls, ldap_key):
189 return cls.query()\
189 return cls.query()\
190 .filter(cls.app_settings_name == ldap_key).scalar()
190 .filter(cls.app_settings_name == ldap_key).scalar()
191
191
192 @classmethod
192 @classmethod
193 def get_app_settings(cls, cache=False):
193 def get_app_settings(cls, cache=False):
194
194
195 ret = cls.query()
195 ret = cls.query()
196
196
197 if cache:
197 if cache:
198 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
198 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
199
199
200 if not ret:
200 if not ret:
201 raise Exception('Could not get application settings !')
201 raise Exception('Could not get application settings !')
202 settings = {}
202 settings = {}
203 for each in ret:
203 for each in ret:
204 settings['rhodecode_' + each.app_settings_name] = \
204 settings['rhodecode_' + each.app_settings_name] = \
205 each.app_settings_value
205 each.app_settings_value
206
206
207 return settings
207 return settings
208
208
209 @classmethod
209 @classmethod
210 def get_ldap_settings(cls, cache=False):
210 def get_ldap_settings(cls, cache=False):
211 ret = cls.query()\
211 ret = cls.query()\
212 .filter(cls.app_settings_name.startswith('ldap_')).all()
212 .filter(cls.app_settings_name.startswith('ldap_')).all()
213 fd = {}
213 fd = {}
214 for row in ret:
214 for row in ret:
215 fd.update({row.app_settings_name:row.app_settings_value})
215 fd.update({row.app_settings_name:row.app_settings_value})
216
216
217 return fd
217 return fd
218
218
219
219
220 class RhodeCodeUi(Base, BaseModel):
220 class RhodeCodeUi(Base, BaseModel):
221 __tablename__ = 'rhodecode_ui'
221 __tablename__ = 'rhodecode_ui'
222 __table_args__ = (
222 __table_args__ = (
223 UniqueConstraint('ui_key'),
223 UniqueConstraint('ui_key'),
224 {'extend_existing': True}
224 {'extend_existing': True}
225 )
225 )
226
226
227 HOOK_UPDATE = 'changegroup.update'
227 HOOK_UPDATE = 'changegroup.update'
228 HOOK_REPO_SIZE = 'changegroup.repo_size'
228 HOOK_REPO_SIZE = 'changegroup.repo_size'
229 HOOK_PUSH = 'pretxnchangegroup.push_logger'
229 HOOK_PUSH = 'pretxnchangegroup.push_logger'
230 HOOK_PULL = 'preoutgoing.pull_logger'
230 HOOK_PULL = 'preoutgoing.pull_logger'
231
231
232 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
232 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
233 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
233 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
234 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
234 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
235 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
235 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
236 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
236 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
237
237
238 @classmethod
238 @classmethod
239 def get_by_key(cls, key):
239 def get_by_key(cls, key):
240 return cls.query().filter(cls.ui_key == key)
240 return cls.query().filter(cls.ui_key == key)
241
241
242 @classmethod
242 @classmethod
243 def get_builtin_hooks(cls):
243 def get_builtin_hooks(cls):
244 q = cls.query()
244 q = cls.query()
245 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
245 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
246 cls.HOOK_REPO_SIZE,
246 cls.HOOK_REPO_SIZE,
247 cls.HOOK_PUSH, cls.HOOK_PULL]))
247 cls.HOOK_PUSH, cls.HOOK_PULL]))
248 return q.all()
248 return q.all()
249
249
250 @classmethod
250 @classmethod
251 def get_custom_hooks(cls):
251 def get_custom_hooks(cls):
252 q = cls.query()
252 q = cls.query()
253 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
253 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
254 cls.HOOK_REPO_SIZE,
254 cls.HOOK_REPO_SIZE,
255 cls.HOOK_PUSH, cls.HOOK_PULL]))
255 cls.HOOK_PUSH, cls.HOOK_PULL]))
256 q = q.filter(cls.ui_section == 'hooks')
256 q = q.filter(cls.ui_section == 'hooks')
257 return q.all()
257 return q.all()
258
258
259 @classmethod
259 @classmethod
260 def create_or_update_hook(cls, key, val):
260 def create_or_update_hook(cls, key, val):
261 new_ui = cls.get_by_key(key).scalar() or cls()
261 new_ui = cls.get_by_key(key).scalar() or cls()
262 new_ui.ui_section = 'hooks'
262 new_ui.ui_section = 'hooks'
263 new_ui.ui_active = True
263 new_ui.ui_active = True
264 new_ui.ui_key = key
264 new_ui.ui_key = key
265 new_ui.ui_value = val
265 new_ui.ui_value = val
266
266
267 Session.add(new_ui)
267 Session.add(new_ui)
268
268
269
269
270 class User(Base, BaseModel):
270 class User(Base, BaseModel):
271 __tablename__ = 'users'
271 __tablename__ = 'users'
272 __table_args__ = (
272 __table_args__ = (
273 UniqueConstraint('username'), UniqueConstraint('email'),
273 UniqueConstraint('username'), UniqueConstraint('email'),
274 {'extend_existing': True}
274 {'extend_existing': True}
275 )
275 )
276 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
276 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
277 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
278 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
278 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
279 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
279 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
280 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
280 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
281 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
281 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
282 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
282 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
283 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
283 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
284 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
284 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
285 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
285 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
287
287
288 user_log = relationship('UserLog', cascade='all')
288 user_log = relationship('UserLog', cascade='all')
289 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
289 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
290
290
291 repositories = relationship('Repository')
291 repositories = relationship('Repository')
292 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
292 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
293 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
293 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
294
294
295 group_member = relationship('UsersGroupMember', cascade='all')
295 group_member = relationship('UsersGroupMember', cascade='all')
296
296
297 notifications = relationship('UserNotification',)
297 notifications = relationship('UserNotification',)
298
298
299 @hybrid_property
299 @hybrid_property
300 def email(self):
300 def email(self):
301 return self._email
301 return self._email
302
302
303 @email.setter
303 @email.setter
304 def email(self, val):
304 def email(self, val):
305 self._email = val.lower() if val else None
305 self._email = val.lower() if val else None
306
306
307 @property
307 @property
308 def full_name(self):
308 def full_name(self):
309 return '%s %s' % (self.name, self.lastname)
309 return '%s %s' % (self.name, self.lastname)
310
310
311 @property
311 @property
312 def full_name_or_username(self):
312 def full_name_or_username(self):
313 return ('%s %s' % (self.name, self.lastname)
313 return ('%s %s' % (self.name, self.lastname)
314 if (self.name and self.lastname) else self.username)
314 if (self.name and self.lastname) else self.username)
315
315
316 @property
316 @property
317 def full_contact(self):
317 def full_contact(self):
318 return '%s %s <%s>' % (self.name, self.lastname, self.email)
318 return '%s %s <%s>' % (self.name, self.lastname, self.email)
319
319
320 @property
320 @property
321 def short_contact(self):
321 def short_contact(self):
322 return '%s %s' % (self.name, self.lastname)
322 return '%s %s' % (self.name, self.lastname)
323
323
324 @property
324 @property
325 def is_admin(self):
325 def is_admin(self):
326 return self.admin
326 return self.admin
327
327
328 def __repr__(self):
328 def __repr__(self):
329 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
329 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
330 self.user_id, self.username)
330 self.user_id, self.username)
331
331
332 @classmethod
332 @classmethod
333 def get_by_username(cls, username, case_insensitive=False, cache=False):
333 def get_by_username(cls, username, case_insensitive=False, cache=False):
334 if case_insensitive:
334 if case_insensitive:
335 q = cls.query().filter(cls.username.ilike(username))
335 q = cls.query().filter(cls.username.ilike(username))
336 else:
336 else:
337 q = cls.query().filter(cls.username == username)
337 q = cls.query().filter(cls.username == username)
338
338
339 if cache:
339 if cache:
340 q = q.options(FromCache("sql_cache_short",
340 q = q.options(FromCache("sql_cache_short",
341 "get_user_%s" % username))
341 "get_user_%s" % username))
342 return q.scalar()
342 return q.scalar()
343
343
344 @classmethod
344 @classmethod
345 def get_by_api_key(cls, api_key, cache=False):
345 def get_by_api_key(cls, api_key, cache=False):
346 q = cls.query().filter(cls.api_key == api_key)
346 q = cls.query().filter(cls.api_key == api_key)
347
347
348 if cache:
348 if cache:
349 q = q.options(FromCache("sql_cache_short",
349 q = q.options(FromCache("sql_cache_short",
350 "get_api_key_%s" % api_key))
350 "get_api_key_%s" % api_key))
351 return q.scalar()
351 return q.scalar()
352
352
353 @classmethod
353 @classmethod
354 def get_by_email(cls, email, case_insensitive=False, cache=False):
354 def get_by_email(cls, email, case_insensitive=False, cache=False):
355 if case_insensitive:
355 if case_insensitive:
356 q = cls.query().filter(cls.email.ilike(email))
356 q = cls.query().filter(cls.email.ilike(email))
357 else:
357 else:
358 q = cls.query().filter(cls.email == email)
358 q = cls.query().filter(cls.email == email)
359
359
360 if cache:
360 if cache:
361 q = q.options(FromCache("sql_cache_short",
361 q = q.options(FromCache("sql_cache_short",
362 "get_api_key_%s" % email))
362 "get_api_key_%s" % email))
363 return q.scalar()
363 return q.scalar()
364
364
365 def update_lastlogin(self):
365 def update_lastlogin(self):
366 """Update user lastlogin"""
366 """Update user lastlogin"""
367 self.last_login = datetime.datetime.now()
367 self.last_login = datetime.datetime.now()
368 Session.add(self)
368 Session.add(self)
369 log.debug('updated user %s lastlogin' % self.username)
369 log.debug('updated user %s lastlogin' % self.username)
370
370
371 def __json__(self):
371 def __json__(self):
372 return dict(
372 return dict(
373 email=self.email,
373 email=self.email,
374 full_name=self.full_name,
374 full_name=self.full_name,
375 full_name_or_username=self.full_name_or_username,
375 full_name_or_username=self.full_name_or_username,
376 short_contact=self.short_contact,
376 short_contact=self.short_contact,
377 full_contact=self.full_contact
377 full_contact=self.full_contact
378 )
378 )
379
379
380
380
381 class UserLog(Base, BaseModel):
381 class UserLog(Base, BaseModel):
382 __tablename__ = 'user_logs'
382 __tablename__ = 'user_logs'
383 __table_args__ = {'extend_existing': True}
383 __table_args__ = {'extend_existing': True}
384 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
384 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
386 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
386 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
387 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
387 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
388 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
388 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
389 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
389 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
390 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
390 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
391
391
392 @property
392 @property
393 def action_as_day(self):
393 def action_as_day(self):
394 return datetime.date(*self.action_date.timetuple()[:3])
394 return datetime.date(*self.action_date.timetuple()[:3])
395
395
396 user = relationship('User')
396 user = relationship('User')
397 repository = relationship('Repository',cascade='')
397 repository = relationship('Repository',cascade='')
398
398
399
399
400 class UsersGroup(Base, BaseModel):
400 class UsersGroup(Base, BaseModel):
401 __tablename__ = 'users_groups'
401 __tablename__ = 'users_groups'
402 __table_args__ = {'extend_existing': True}
402 __table_args__ = {'extend_existing': True}
403
403
404 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
404 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
405 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
405 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
406 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
406 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
407
407
408 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
408 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
409
409
410 def __repr__(self):
410 def __repr__(self):
411 return '<userGroup(%s)>' % (self.users_group_name)
411 return '<userGroup(%s)>' % (self.users_group_name)
412
412
413 @classmethod
413 @classmethod
414 def get_by_group_name(cls, group_name, cache=False,
414 def get_by_group_name(cls, group_name, cache=False,
415 case_insensitive=False):
415 case_insensitive=False):
416 if case_insensitive:
416 if case_insensitive:
417 q = cls.query().filter(cls.users_group_name.ilike(group_name))
417 q = cls.query().filter(cls.users_group_name.ilike(group_name))
418 else:
418 else:
419 q = cls.query().filter(cls.users_group_name == group_name)
419 q = cls.query().filter(cls.users_group_name == group_name)
420 if cache:
420 if cache:
421 q = q.options(FromCache("sql_cache_short",
421 q = q.options(FromCache("sql_cache_short",
422 "get_user_%s" % group_name))
422 "get_user_%s" % group_name))
423 return q.scalar()
423 return q.scalar()
424
424
425 @classmethod
425 @classmethod
426 def get(cls, users_group_id, cache=False):
426 def get(cls, users_group_id, cache=False):
427 users_group = cls.query()
427 users_group = cls.query()
428 if cache:
428 if cache:
429 users_group = users_group.options(FromCache("sql_cache_short",
429 users_group = users_group.options(FromCache("sql_cache_short",
430 "get_users_group_%s" % users_group_id))
430 "get_users_group_%s" % users_group_id))
431 return users_group.get(users_group_id)
431 return users_group.get(users_group_id)
432
432
433
433
434 class UsersGroupMember(Base, BaseModel):
434 class UsersGroupMember(Base, BaseModel):
435 __tablename__ = 'users_groups_members'
435 __tablename__ = 'users_groups_members'
436 __table_args__ = {'extend_existing': True}
436 __table_args__ = {'extend_existing': True}
437
437
438 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
438 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
439 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
439 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
440 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
440 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
441
441
442 user = relationship('User', lazy='joined')
442 user = relationship('User', lazy='joined')
443 users_group = relationship('UsersGroup')
443 users_group = relationship('UsersGroup')
444
444
445 def __init__(self, gr_id='', u_id=''):
445 def __init__(self, gr_id='', u_id=''):
446 self.users_group_id = gr_id
446 self.users_group_id = gr_id
447 self.user_id = u_id
447 self.user_id = u_id
448
448
449 @staticmethod
449 @staticmethod
450 def add_user_to_group(group, user):
450 def add_user_to_group(group, user):
451 ugm = UsersGroupMember()
451 ugm = UsersGroupMember()
452 ugm.users_group = group
452 ugm.users_group = group
453 ugm.user = user
453 ugm.user = user
454 Session.add(ugm)
454 Session.add(ugm)
455 Session.commit()
455 Session.commit()
456 return ugm
456 return ugm
457
457
458
458
459 class Repository(Base, BaseModel):
459 class Repository(Base, BaseModel):
460 __tablename__ = 'repositories'
460 __tablename__ = 'repositories'
461 __table_args__ = (
461 __table_args__ = (
462 UniqueConstraint('repo_name'),
462 UniqueConstraint('repo_name'),
463 {'extend_existing': True},
463 {'extend_existing': True},
464 )
464 )
465
465
466 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
466 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
467 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
467 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
468 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
468 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
469 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
469 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
471 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
471 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
472 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
472 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
473 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
473 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
474 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
474 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
475 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
475 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
476
476
477 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
477 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
478 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
478 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
479
479
480 user = relationship('User')
480 user = relationship('User')
481 fork = relationship('Repository', remote_side=repo_id)
481 fork = relationship('Repository', remote_side=repo_id)
482 group = relationship('RepoGroup')
482 group = relationship('RepoGroup')
483 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
483 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
484 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
484 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
485 stats = relationship('Statistics', cascade='all', uselist=False)
485 stats = relationship('Statistics', cascade='all', uselist=False)
486
486
487 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
487 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
488
488
489 logs = relationship('UserLog')
489 logs = relationship('UserLog')
490
490
491 def __repr__(self):
491 def __repr__(self):
492 return "<%s('%s:%s')>" % (self.__class__.__name__,
492 return "<%s('%s:%s')>" % (self.__class__.__name__,
493 self.repo_id, self.repo_name)
493 self.repo_id, self.repo_name)
494
494
495 @classmethod
495 @classmethod
496 def url_sep(cls):
496 def url_sep(cls):
497 return '/'
497 return '/'
498
498
499 @classmethod
499 @classmethod
500 def get_by_repo_name(cls, repo_name):
500 def get_by_repo_name(cls, repo_name):
501 q = Session.query(cls).filter(cls.repo_name == repo_name)
501 q = Session.query(cls).filter(cls.repo_name == repo_name)
502 q = q.options(joinedload(Repository.fork))\
502 q = q.options(joinedload(Repository.fork))\
503 .options(joinedload(Repository.user))\
503 .options(joinedload(Repository.user))\
504 .options(joinedload(Repository.group))
504 .options(joinedload(Repository.group))
505 return q.scalar()
505 return q.scalar()
506
506
507 @classmethod
507 @classmethod
508 def get_repo_forks(cls, repo_id):
508 def get_repo_forks(cls, repo_id):
509 return cls.query().filter(Repository.fork_id == repo_id)
509 return cls.query().filter(Repository.fork_id == repo_id)
510
510
511 @classmethod
511 @classmethod
512 def base_path(cls):
512 def base_path(cls):
513 """
513 """
514 Returns base path when all repos are stored
514 Returns base path when all repos are stored
515
515
516 :param cls:
516 :param cls:
517 """
517 """
518 q = Session.query(RhodeCodeUi)\
518 q = Session.query(RhodeCodeUi)\
519 .filter(RhodeCodeUi.ui_key == cls.url_sep())
519 .filter(RhodeCodeUi.ui_key == cls.url_sep())
520 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
520 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
521 return q.one().ui_value
521 return q.one().ui_value
522
522
523 @property
523 @property
524 def just_name(self):
524 def just_name(self):
525 return self.repo_name.split(Repository.url_sep())[-1]
525 return self.repo_name.split(Repository.url_sep())[-1]
526
526
527 @property
527 @property
528 def groups_with_parents(self):
528 def groups_with_parents(self):
529 groups = []
529 groups = []
530 if self.group is None:
530 if self.group is None:
531 return groups
531 return groups
532
532
533 cur_gr = self.group
533 cur_gr = self.group
534 groups.insert(0, cur_gr)
534 groups.insert(0, cur_gr)
535 while 1:
535 while 1:
536 gr = getattr(cur_gr, 'parent_group', None)
536 gr = getattr(cur_gr, 'parent_group', None)
537 cur_gr = cur_gr.parent_group
537 cur_gr = cur_gr.parent_group
538 if gr is None:
538 if gr is None:
539 break
539 break
540 groups.insert(0, gr)
540 groups.insert(0, gr)
541
541
542 return groups
542 return groups
543
543
544 @property
544 @property
545 def groups_and_repo(self):
545 def groups_and_repo(self):
546 return self.groups_with_parents, self.just_name
546 return self.groups_with_parents, self.just_name
547
547
548 @LazyProperty
548 @LazyProperty
549 def repo_path(self):
549 def repo_path(self):
550 """
550 """
551 Returns base full path for that repository means where it actually
551 Returns base full path for that repository means where it actually
552 exists on a filesystem
552 exists on a filesystem
553 """
553 """
554 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
554 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
555 Repository.url_sep())
555 Repository.url_sep())
556 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
556 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
557 return q.one().ui_value
557 return q.one().ui_value
558
558
559 @property
559 @property
560 def repo_full_path(self):
560 def repo_full_path(self):
561 p = [self.repo_path]
561 p = [self.repo_path]
562 # we need to split the name by / since this is how we store the
562 # we need to split the name by / since this is how we store the
563 # names in the database, but that eventually needs to be converted
563 # names in the database, but that eventually needs to be converted
564 # into a valid system path
564 # into a valid system path
565 p += self.repo_name.split(Repository.url_sep())
565 p += self.repo_name.split(Repository.url_sep())
566 return os.path.join(*p)
566 return os.path.join(*p)
567
567
568 def get_new_name(self, repo_name):
568 def get_new_name(self, repo_name):
569 """
569 """
570 returns new full repository name based on assigned group and new new
570 returns new full repository name based on assigned group and new new
571
571
572 :param group_name:
572 :param group_name:
573 """
573 """
574 path_prefix = self.group.full_path_splitted if self.group else []
574 path_prefix = self.group.full_path_splitted if self.group else []
575 return Repository.url_sep().join(path_prefix + [repo_name])
575 return Repository.url_sep().join(path_prefix + [repo_name])
576
576
577 @property
577 @property
578 def _ui(self):
578 def _ui(self):
579 """
579 """
580 Creates an db based ui object for this repository
580 Creates an db based ui object for this repository
581 """
581 """
582 from mercurial import ui
582 from mercurial import ui
583 from mercurial import config
583 from mercurial import config
584 baseui = ui.ui()
584 baseui = ui.ui()
585
585
586 #clean the baseui object
586 #clean the baseui object
587 baseui._ocfg = config.config()
587 baseui._ocfg = config.config()
588 baseui._ucfg = config.config()
588 baseui._ucfg = config.config()
589 baseui._tcfg = config.config()
589 baseui._tcfg = config.config()
590
590
591 ret = RhodeCodeUi.query()\
591 ret = RhodeCodeUi.query()\
592 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
592 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
593
593
594 hg_ui = ret
594 hg_ui = ret
595 for ui_ in hg_ui:
595 for ui_ in hg_ui:
596 if ui_.ui_active:
596 if ui_.ui_active:
597 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
597 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
598 ui_.ui_key, ui_.ui_value)
598 ui_.ui_key, ui_.ui_value)
599 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
599 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
600
600
601 return baseui
601 return baseui
602
602
603 @classmethod
603 @classmethod
604 def is_valid(cls, repo_name):
604 def is_valid(cls, repo_name):
605 """
605 """
606 returns True if given repo name is a valid filesystem repository
606 returns True if given repo name is a valid filesystem repository
607
607
608 :param cls:
608 :param cls:
609 :param repo_name:
609 :param repo_name:
610 """
610 """
611 from rhodecode.lib.utils import is_valid_repo
611 from rhodecode.lib.utils import is_valid_repo
612
612
613 return is_valid_repo(repo_name, cls.base_path())
613 return is_valid_repo(repo_name, cls.base_path())
614
614
615 #==========================================================================
615 #==========================================================================
616 # SCM PROPERTIES
616 # SCM PROPERTIES
617 #==========================================================================
617 #==========================================================================
618
618
619 def get_changeset(self, rev):
619 def get_changeset(self, rev):
620 return get_changeset_safe(self.scm_instance, rev)
620 return get_changeset_safe(self.scm_instance, rev)
621
621
622 @property
622 @property
623 def tip(self):
623 def tip(self):
624 return self.get_changeset('tip')
624 return self.get_changeset('tip')
625
625
626 @property
626 @property
627 def author(self):
627 def author(self):
628 return self.tip.author
628 return self.tip.author
629
629
630 @property
630 @property
631 def last_change(self):
631 def last_change(self):
632 return self.scm_instance.last_change
632 return self.scm_instance.last_change
633
633
634 def comments(self, revisions=None):
634 def comments(self, revisions=None):
635 """
635 """
636 Returns comments for this repository grouped by revisions
636 Returns comments for this repository grouped by revisions
637
637
638 :param revisions: filter query by revisions only
638 :param revisions: filter query by revisions only
639 """
639 """
640 cmts = ChangesetComment.query()\
640 cmts = ChangesetComment.query()\
641 .filter(ChangesetComment.repo == self)
641 .filter(ChangesetComment.repo == self)
642 if revisions:
642 if revisions:
643 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
643 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
644 grouped = defaultdict(list)
644 grouped = defaultdict(list)
645 for cmt in cmts.all():
645 for cmt in cmts.all():
646 grouped[cmt.revision].append(cmt)
646 grouped[cmt.revision].append(cmt)
647 return grouped
647 return grouped
648
648
649 #==========================================================================
649 #==========================================================================
650 # SCM CACHE INSTANCE
650 # SCM CACHE INSTANCE
651 #==========================================================================
651 #==========================================================================
652
652
653 @property
653 @property
654 def invalidate(self):
654 def invalidate(self):
655 return CacheInvalidation.invalidate(self.repo_name)
655 return CacheInvalidation.invalidate(self.repo_name)
656
656
657 def set_invalidate(self):
657 def set_invalidate(self):
658 """
658 """
659 set a cache for invalidation for this instance
659 set a cache for invalidation for this instance
660 """
660 """
661 CacheInvalidation.set_invalidate(self.repo_name)
661 CacheInvalidation.set_invalidate(self.repo_name)
662
662
663 @LazyProperty
663 @LazyProperty
664 def scm_instance(self):
664 def scm_instance(self):
665 return self.__get_instance()
665 return self.__get_instance()
666
666
667 @property
667 @property
668 def scm_instance_cached(self):
668 def scm_instance_cached(self):
669 @cache_region('long_term')
669 @cache_region('long_term')
670 def _c(repo_name):
670 def _c(repo_name):
671 return self.__get_instance()
671 return self.__get_instance()
672 rn = self.repo_name
672 rn = self.repo_name
673 log.debug('Getting cached instance of repo')
673 log.debug('Getting cached instance of repo')
674 inv = self.invalidate
674 inv = self.invalidate
675 if inv is not None:
675 if inv is not None:
676 region_invalidate(_c, None, rn)
676 region_invalidate(_c, None, rn)
677 # update our cache
677 # update our cache
678 CacheInvalidation.set_valid(inv.cache_key)
678 CacheInvalidation.set_valid(inv.cache_key)
679 return _c(rn)
679 return _c(rn)
680
680
681 def __get_instance(self):
681 def __get_instance(self):
682 repo_full_path = self.repo_full_path
682 repo_full_path = self.repo_full_path
683 try:
683 try:
684 alias = get_scm(repo_full_path)[0]
684 alias = get_scm(repo_full_path)[0]
685 log.debug('Creating instance of %s repository' % alias)
685 log.debug('Creating instance of %s repository' % alias)
686 backend = get_backend(alias)
686 backend = get_backend(alias)
687 except VCSError:
687 except VCSError:
688 log.error(traceback.format_exc())
688 log.error(traceback.format_exc())
689 log.error('Perhaps this repository is in db and not in '
689 log.error('Perhaps this repository is in db and not in '
690 'filesystem run rescan repositories with '
690 'filesystem run rescan repositories with '
691 '"destroy old data " option from admin panel')
691 '"destroy old data " option from admin panel')
692 return
692 return
693
693
694 if alias == 'hg':
694 if alias == 'hg':
695 repo = backend(safe_str(repo_full_path), create=False,
695 repo = backend(safe_str(repo_full_path), create=False,
696 baseui=self._ui)
696 baseui=self._ui)
697 # skip hidden web repository
697 # skip hidden web repository
698 if repo._get_hidden():
698 if repo._get_hidden():
699 return
699 return
700 else:
700 else:
701 repo = backend(repo_full_path, create=False)
701 repo = backend(repo_full_path, create=False)
702
702
703 return repo
703 return repo
704
704
705
705
706 class RepoGroup(Base, BaseModel):
706 class RepoGroup(Base, BaseModel):
707 __tablename__ = 'groups'
707 __tablename__ = 'groups'
708 __table_args__ = (
708 __table_args__ = (
709 UniqueConstraint('group_name', 'group_parent_id'),
709 UniqueConstraint('group_name', 'group_parent_id'),
710 CheckConstraint('group_id != group_parent_id'),
710 CheckConstraint('group_id != group_parent_id'),
711 {'extend_existing': True},
711 {'extend_existing': True},
712 )
712 )
713 __mapper_args__ = {'order_by': 'group_name'}
713 __mapper_args__ = {'order_by': 'group_name'}
714
714
715 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
715 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
716 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
716 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
717 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
717 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
718 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
718 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
719
719
720 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
721 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
722
720 parent_group = relationship('RepoGroup', remote_side=group_id)
723 parent_group = relationship('RepoGroup', remote_side=group_id)
721
724
722 def __init__(self, group_name='', parent_group=None):
725 def __init__(self, group_name='', parent_group=None):
723 self.group_name = group_name
726 self.group_name = group_name
724 self.parent_group = parent_group
727 self.parent_group = parent_group
725
728
726 def __repr__(self):
729 def __repr__(self):
727 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
730 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
728 self.group_name)
731 self.group_name)
729
732
730 @classmethod
733 @classmethod
731 def groups_choices(cls):
734 def groups_choices(cls):
732 from webhelpers.html import literal as _literal
735 from webhelpers.html import literal as _literal
733 repo_groups = [('', '')]
736 repo_groups = [('', '')]
734 sep = ' &raquo; '
737 sep = ' &raquo; '
735 _name = lambda k: _literal(sep.join(k))
738 _name = lambda k: _literal(sep.join(k))
736
739
737 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
740 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
738 for x in cls.query().all()])
741 for x in cls.query().all()])
739
742
740 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
743 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
741 return repo_groups
744 return repo_groups
742
745
743 @classmethod
746 @classmethod
744 def url_sep(cls):
747 def url_sep(cls):
745 return '/'
748 return '/'
746
749
747 @classmethod
750 @classmethod
748 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
751 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
749 if case_insensitive:
752 if case_insensitive:
750 gr = cls.query()\
753 gr = cls.query()\
751 .filter(cls.group_name.ilike(group_name))
754 .filter(cls.group_name.ilike(group_name))
752 else:
755 else:
753 gr = cls.query()\
756 gr = cls.query()\
754 .filter(cls.group_name == group_name)
757 .filter(cls.group_name == group_name)
755 if cache:
758 if cache:
756 gr = gr.options(FromCache("sql_cache_short",
759 gr = gr.options(FromCache("sql_cache_short",
757 "get_group_%s" % group_name))
760 "get_group_%s" % group_name))
758 return gr.scalar()
761 return gr.scalar()
759
762
760 @property
763 @property
761 def parents(self):
764 def parents(self):
762 parents_recursion_limit = 5
765 parents_recursion_limit = 5
763 groups = []
766 groups = []
764 if self.parent_group is None:
767 if self.parent_group is None:
765 return groups
768 return groups
766 cur_gr = self.parent_group
769 cur_gr = self.parent_group
767 groups.insert(0, cur_gr)
770 groups.insert(0, cur_gr)
768 cnt = 0
771 cnt = 0
769 while 1:
772 while 1:
770 cnt += 1
773 cnt += 1
771 gr = getattr(cur_gr, 'parent_group', None)
774 gr = getattr(cur_gr, 'parent_group', None)
772 cur_gr = cur_gr.parent_group
775 cur_gr = cur_gr.parent_group
773 if gr is None:
776 if gr is None:
774 break
777 break
775 if cnt == parents_recursion_limit:
778 if cnt == parents_recursion_limit:
776 # this will prevent accidental infinit loops
779 # this will prevent accidental infinit loops
777 log.error('group nested more than %s' %
780 log.error('group nested more than %s' %
778 parents_recursion_limit)
781 parents_recursion_limit)
779 break
782 break
780
783
781 groups.insert(0, gr)
784 groups.insert(0, gr)
782 return groups
785 return groups
783
786
784 @property
787 @property
785 def children(self):
788 def children(self):
786 return RepoGroup.query().filter(RepoGroup.parent_group == self)
789 return RepoGroup.query().filter(RepoGroup.parent_group == self)
787
790
788 @property
791 @property
789 def name(self):
792 def name(self):
790 return self.group_name.split(RepoGroup.url_sep())[-1]
793 return self.group_name.split(RepoGroup.url_sep())[-1]
791
794
792 @property
795 @property
793 def full_path(self):
796 def full_path(self):
794 return self.group_name
797 return self.group_name
795
798
796 @property
799 @property
797 def full_path_splitted(self):
800 def full_path_splitted(self):
798 return self.group_name.split(RepoGroup.url_sep())
801 return self.group_name.split(RepoGroup.url_sep())
799
802
800 @property
803 @property
801 def repositories(self):
804 def repositories(self):
802 return Repository.query().filter(Repository.group == self)
805 return Repository.query().filter(Repository.group == self)
803
806
804 @property
807 @property
805 def repositories_recursive_count(self):
808 def repositories_recursive_count(self):
806 cnt = self.repositories.count()
809 cnt = self.repositories.count()
807
810
808 def children_count(group):
811 def children_count(group):
809 cnt = 0
812 cnt = 0
810 for child in group.children:
813 for child in group.children:
811 cnt += child.repositories.count()
814 cnt += child.repositories.count()
812 cnt += children_count(child)
815 cnt += children_count(child)
813 return cnt
816 return cnt
814
817
815 return cnt + children_count(self)
818 return cnt + children_count(self)
816
819
817 def get_new_name(self, group_name):
820 def get_new_name(self, group_name):
818 """
821 """
819 returns new full group name based on parent and new name
822 returns new full group name based on parent and new name
820
823
821 :param group_name:
824 :param group_name:
822 """
825 """
823 path_prefix = (self.parent_group.full_path_splitted if
826 path_prefix = (self.parent_group.full_path_splitted if
824 self.parent_group else [])
827 self.parent_group else [])
825 return RepoGroup.url_sep().join(path_prefix + [group_name])
828 return RepoGroup.url_sep().join(path_prefix + [group_name])
826
829
827
830
828 class Permission(Base, BaseModel):
831 class Permission(Base, BaseModel):
829 __tablename__ = 'permissions'
832 __tablename__ = 'permissions'
830 __table_args__ = {'extend_existing': True}
833 __table_args__ = {'extend_existing': True}
831 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
834 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
832 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
835 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
833 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
836 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
834
837
835 def __repr__(self):
838 def __repr__(self):
836 return "<%s('%s:%s')>" % (self.__class__.__name__,
839 return "<%s('%s:%s')>" % (
837 self.permission_id, self.permission_name)
840 self.__class__.__name__, self.permission_id, self.permission_name
841 )
838
842
839 @classmethod
843 @classmethod
840 def get_by_key(cls, key):
844 def get_by_key(cls, key):
841 return cls.query().filter(cls.permission_name == key).scalar()
845 return cls.query().filter(cls.permission_name == key).scalar()
842
846
843 @classmethod
847 @classmethod
844 def get_default_perms(cls, default_user_id):
848 def get_default_perms(cls, default_user_id):
845 q = Session.query(UserRepoToPerm, Repository, cls)\
849 q = Session.query(UserRepoToPerm, Repository, cls)\
846 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
850 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
847 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
851 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
848 .filter(UserRepoToPerm.user_id == default_user_id)
852 .filter(UserRepoToPerm.user_id == default_user_id)
853
854 return q.all()
855
856 @classmethod
857 def get_default_group_perms(cls, default_user_id):
858 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
859 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
860 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
861 .filter(UserRepoGroupToPerm.user_id == default_user_id)
849
862
850 return q.all()
863 return q.all()
851
864
852
865
853 class UserRepoToPerm(Base, BaseModel):
866 class UserRepoToPerm(Base, BaseModel):
854 __tablename__ = 'repo_to_perm'
867 __tablename__ = 'repo_to_perm'
855 __table_args__ = (
868 __table_args__ = (
856 UniqueConstraint('user_id', 'repository_id'), {'extend_existing': True}
869 UniqueConstraint('user_id', 'repository_id'), {'extend_existing': True}
857 )
870 )
858 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
871 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
859 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
872 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
860 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
873 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
861 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
874 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
862
875
863 user = relationship('User')
876 user = relationship('User')
864 permission = relationship('Permission')
877 permission = relationship('Permission')
865 repository = relationship('Repository')
878 repository = relationship('Repository')
866
879
867 @classmethod
880 @classmethod
868 def create(cls, user, repository, permission):
881 def create(cls, user, repository, permission):
869 n = cls()
882 n = cls()
870 n.user = user
883 n.user = user
871 n.repository = repository
884 n.repository = repository
872 n.permission = permission
885 n.permission = permission
873 Session.add(n)
886 Session.add(n)
874 return n
887 return n
875
888
876 def __repr__(self):
889 def __repr__(self):
877 return '<user:%s => %s >' % (self.user, self.repository)
890 return '<user:%s => %s >' % (self.user, self.repository)
878
891
879
892
880 class UserToPerm(Base, BaseModel):
893 class UserToPerm(Base, BaseModel):
881 __tablename__ = 'user_to_perm'
894 __tablename__ = 'user_to_perm'
882 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing': True})
895 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing': True})
883 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
896 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
884 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
897 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
885 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
898 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
886
899
887 user = relationship('User')
900 user = relationship('User')
888 permission = relationship('Permission', lazy='joined')
901 permission = relationship('Permission', lazy='joined')
889
902
890
903
891 class UsersGroupRepoToPerm(Base, BaseModel):
904 class UsersGroupRepoToPerm(Base, BaseModel):
892 __tablename__ = 'users_group_repo_to_perm'
905 __tablename__ = 'users_group_repo_to_perm'
893 __table_args__ = (
906 __table_args__ = (
894 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
907 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
895 {'extend_existing': True}
908 {'extend_existing': True}
896 )
909 )
897 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
910 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
898 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
911 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
899 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
912 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
900 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
913 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
901
914
902 users_group = relationship('UsersGroup')
915 users_group = relationship('UsersGroup')
903 permission = relationship('Permission')
916 permission = relationship('Permission')
904 repository = relationship('Repository')
917 repository = relationship('Repository')
905
918
906 @classmethod
919 @classmethod
907 def create(cls, users_group, repository, permission):
920 def create(cls, users_group, repository, permission):
908 n = cls()
921 n = cls()
909 n.users_group = users_group
922 n.users_group = users_group
910 n.repository = repository
923 n.repository = repository
911 n.permission = permission
924 n.permission = permission
912 Session.add(n)
925 Session.add(n)
913 return n
926 return n
914
927
915 def __repr__(self):
928 def __repr__(self):
916 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
929 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
917
930
918
931
919 class UsersGroupToPerm(Base, BaseModel):
932 class UsersGroupToPerm(Base, BaseModel):
920 __tablename__ = 'users_group_to_perm'
933 __tablename__ = 'users_group_to_perm'
921 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
934 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
922 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
935 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
923 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
936 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
924
937
925 users_group = relationship('UsersGroup')
938 users_group = relationship('UsersGroup')
926 permission = relationship('Permission')
939 permission = relationship('Permission')
927
940
928
941
929 class UserRepoGroupToPerm(Base, BaseModel):
942 class UserRepoGroupToPerm(Base, BaseModel):
930 __tablename__ = 'user_repo_group_to_perm'
943 __tablename__ = 'user_repo_group_to_perm'
931 __table_args__ = (
944 __table_args__ = (
932 UniqueConstraint('group_id', 'permission_id'),
945 UniqueConstraint('group_id', 'permission_id'),
933 {'extend_existing': True}
946 {'extend_existing': True}
934 )
947 )
935
948
936 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
949 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
950 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
938 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
951 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
939 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
952 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
940
953
941 user = relationship('User')
954 user = relationship('User')
942 permission = relationship('Permission')
955 permission = relationship('Permission')
943 group = relationship('RepoGroup')
956 group = relationship('RepoGroup')
944
957
945
958
946 class UsersGroupRepoGroupToPerm(Base, BaseModel):
959 class UsersGroupRepoGroupToPerm(Base, BaseModel):
947 __tablename__ = 'users_group_repo_group_to_perm'
960 __tablename__ = 'users_group_repo_group_to_perm'
948 __table_args__ = (
961 __table_args__ = (
949 UniqueConstraint('group_id', 'permission_id'),
962 UniqueConstraint('group_id', 'permission_id'),
950 {'extend_existing': True}
963 {'extend_existing': True}
951 )
964 )
952
965
953 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
966 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
954 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
967 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
955 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
968 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
956 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
969 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
957
970
958 users_group = relationship('UsersGroup')
971 users_group = relationship('UsersGroup')
959 permission = relationship('Permission')
972 permission = relationship('Permission')
960 group = relationship('RepoGroup')
973 group = relationship('RepoGroup')
961
974
962
975
963 class Statistics(Base, BaseModel):
976 class Statistics(Base, BaseModel):
964 __tablename__ = 'statistics'
977 __tablename__ = 'statistics'
965 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
978 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
966 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
979 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
967 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
980 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
968 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
981 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
969 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
982 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
970 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
983 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
971 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
984 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
972
985
973 repository = relationship('Repository', single_parent=True)
986 repository = relationship('Repository', single_parent=True)
974
987
975
988
976 class UserFollowing(Base, BaseModel):
989 class UserFollowing(Base, BaseModel):
977 __tablename__ = 'user_followings'
990 __tablename__ = 'user_followings'
978 __table_args__ = (
991 __table_args__ = (
979 UniqueConstraint('user_id', 'follows_repository_id'),
992 UniqueConstraint('user_id', 'follows_repository_id'),
980 UniqueConstraint('user_id', 'follows_user_id'),
993 UniqueConstraint('user_id', 'follows_user_id'),
981 {'extend_existing': True}
994 {'extend_existing': True}
982 )
995 )
983
996
984 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
997 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
985 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
998 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
986 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
999 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
987 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1000 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
988 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1001 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
989
1002
990 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1003 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
991
1004
992 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1005 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
993 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1006 follows_repository = relationship('Repository', order_by='Repository.repo_name')
994
1007
995 @classmethod
1008 @classmethod
996 def get_repo_followers(cls, repo_id):
1009 def get_repo_followers(cls, repo_id):
997 return cls.query().filter(cls.follows_repo_id == repo_id)
1010 return cls.query().filter(cls.follows_repo_id == repo_id)
998
1011
999
1012
1000 class CacheInvalidation(Base, BaseModel):
1013 class CacheInvalidation(Base, BaseModel):
1001 __tablename__ = 'cache_invalidation'
1014 __tablename__ = 'cache_invalidation'
1002 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
1015 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
1003 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1016 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1004 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1017 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1005 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1018 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1006 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1019 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1007
1020
1008 def __init__(self, cache_key, cache_args=''):
1021 def __init__(self, cache_key, cache_args=''):
1009 self.cache_key = cache_key
1022 self.cache_key = cache_key
1010 self.cache_args = cache_args
1023 self.cache_args = cache_args
1011 self.cache_active = False
1024 self.cache_active = False
1012
1025
1013 def __repr__(self):
1026 def __repr__(self):
1014 return "<%s('%s:%s')>" % (self.__class__.__name__,
1027 return "<%s('%s:%s')>" % (self.__class__.__name__,
1015 self.cache_id, self.cache_key)
1028 self.cache_id, self.cache_key)
1016
1029
1017 @classmethod
1030 @classmethod
1018 def invalidate(cls, key):
1031 def invalidate(cls, key):
1019 """
1032 """
1020 Returns Invalidation object if this given key should be invalidated
1033 Returns Invalidation object if this given key should be invalidated
1021 None otherwise. `cache_active = False` means that this cache
1034 None otherwise. `cache_active = False` means that this cache
1022 state is not valid and needs to be invalidated
1035 state is not valid and needs to be invalidated
1023
1036
1024 :param key:
1037 :param key:
1025 """
1038 """
1026 return cls.query()\
1039 return cls.query()\
1027 .filter(CacheInvalidation.cache_key == key)\
1040 .filter(CacheInvalidation.cache_key == key)\
1028 .filter(CacheInvalidation.cache_active == False)\
1041 .filter(CacheInvalidation.cache_active == False)\
1029 .scalar()
1042 .scalar()
1030
1043
1031 @classmethod
1044 @classmethod
1032 def set_invalidate(cls, key):
1045 def set_invalidate(cls, key):
1033 """
1046 """
1034 Mark this Cache key for invalidation
1047 Mark this Cache key for invalidation
1035
1048
1036 :param key:
1049 :param key:
1037 """
1050 """
1038
1051
1039 log.debug('marking %s for invalidation' % key)
1052 log.debug('marking %s for invalidation' % key)
1040 inv_obj = Session.query(cls)\
1053 inv_obj = Session.query(cls)\
1041 .filter(cls.cache_key == key).scalar()
1054 .filter(cls.cache_key == key).scalar()
1042 if inv_obj:
1055 if inv_obj:
1043 inv_obj.cache_active = False
1056 inv_obj.cache_active = False
1044 else:
1057 else:
1045 log.debug('cache key not found in invalidation db -> creating one')
1058 log.debug('cache key not found in invalidation db -> creating one')
1046 inv_obj = CacheInvalidation(key)
1059 inv_obj = CacheInvalidation(key)
1047
1060
1048 try:
1061 try:
1049 Session.add(inv_obj)
1062 Session.add(inv_obj)
1050 Session.commit()
1063 Session.commit()
1051 except Exception:
1064 except Exception:
1052 log.error(traceback.format_exc())
1065 log.error(traceback.format_exc())
1053 Session.rollback()
1066 Session.rollback()
1054
1067
1055 @classmethod
1068 @classmethod
1056 def set_valid(cls, key):
1069 def set_valid(cls, key):
1057 """
1070 """
1058 Mark this cache key as active and currently cached
1071 Mark this cache key as active and currently cached
1059
1072
1060 :param key:
1073 :param key:
1061 """
1074 """
1062 inv_obj = CacheInvalidation.query()\
1075 inv_obj = CacheInvalidation.query()\
1063 .filter(CacheInvalidation.cache_key == key).scalar()
1076 .filter(CacheInvalidation.cache_key == key).scalar()
1064 inv_obj.cache_active = True
1077 inv_obj.cache_active = True
1065 Session.add(inv_obj)
1078 Session.add(inv_obj)
1066 Session.commit()
1079 Session.commit()
1067
1080
1068
1081
1069 class ChangesetComment(Base, BaseModel):
1082 class ChangesetComment(Base, BaseModel):
1070 __tablename__ = 'changeset_comments'
1083 __tablename__ = 'changeset_comments'
1071 __table_args__ = ({'extend_existing': True},)
1084 __table_args__ = ({'extend_existing': True},)
1072 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1085 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1073 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1086 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1074 revision = Column('revision', String(40), nullable=False)
1087 revision = Column('revision', String(40), nullable=False)
1075 line_no = Column('line_no', Unicode(10), nullable=True)
1088 line_no = Column('line_no', Unicode(10), nullable=True)
1076 f_path = Column('f_path', Unicode(1000), nullable=True)
1089 f_path = Column('f_path', Unicode(1000), nullable=True)
1077 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1090 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1078 text = Column('text', Unicode(25000), nullable=False)
1091 text = Column('text', Unicode(25000), nullable=False)
1079 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1092 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1080
1093
1081 author = relationship('User', lazy='joined')
1094 author = relationship('User', lazy='joined')
1082 repo = relationship('Repository')
1095 repo = relationship('Repository')
1083
1096
1084 @classmethod
1097 @classmethod
1085 def get_users(cls, revision):
1098 def get_users(cls, revision):
1086 """
1099 """
1087 Returns user associated with this changesetComment. ie those
1100 Returns user associated with this changesetComment. ie those
1088 who actually commented
1101 who actually commented
1089
1102
1090 :param cls:
1103 :param cls:
1091 :param revision:
1104 :param revision:
1092 """
1105 """
1093 return Session.query(User)\
1106 return Session.query(User)\
1094 .filter(cls.revision == revision)\
1107 .filter(cls.revision == revision)\
1095 .join(ChangesetComment.author).all()
1108 .join(ChangesetComment.author).all()
1096
1109
1097
1110
1098 class Notification(Base, BaseModel):
1111 class Notification(Base, BaseModel):
1099 __tablename__ = 'notifications'
1112 __tablename__ = 'notifications'
1100 __table_args__ = ({'extend_existing': True},)
1113 __table_args__ = ({'extend_existing': True},)
1101
1114
1102 TYPE_CHANGESET_COMMENT = u'cs_comment'
1115 TYPE_CHANGESET_COMMENT = u'cs_comment'
1103 TYPE_MESSAGE = u'message'
1116 TYPE_MESSAGE = u'message'
1104 TYPE_MENTION = u'mention'
1117 TYPE_MENTION = u'mention'
1105 TYPE_REGISTRATION = u'registration'
1118 TYPE_REGISTRATION = u'registration'
1106
1119
1107 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1120 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1108 subject = Column('subject', Unicode(512), nullable=True)
1121 subject = Column('subject', Unicode(512), nullable=True)
1109 body = Column('body', Unicode(50000), nullable=True)
1122 body = Column('body', Unicode(50000), nullable=True)
1110 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1123 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1111 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1124 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1112 type_ = Column('type', Unicode(256))
1125 type_ = Column('type', Unicode(256))
1113
1126
1114 created_by_user = relationship('User')
1127 created_by_user = relationship('User')
1115 notifications_to_users = relationship('UserNotification', lazy='joined',
1128 notifications_to_users = relationship('UserNotification', lazy='joined',
1116 cascade="all, delete, delete-orphan")
1129 cascade="all, delete, delete-orphan")
1117
1130
1118 @property
1131 @property
1119 def recipients(self):
1132 def recipients(self):
1120 return [x.user for x in UserNotification.query()\
1133 return [x.user for x in UserNotification.query()\
1121 .filter(UserNotification.notification == self).all()]
1134 .filter(UserNotification.notification == self).all()]
1122
1135
1123 @classmethod
1136 @classmethod
1124 def create(cls, created_by, subject, body, recipients, type_=None):
1137 def create(cls, created_by, subject, body, recipients, type_=None):
1125 if type_ is None:
1138 if type_ is None:
1126 type_ = Notification.TYPE_MESSAGE
1139 type_ = Notification.TYPE_MESSAGE
1127
1140
1128 notification = cls()
1141 notification = cls()
1129 notification.created_by_user = created_by
1142 notification.created_by_user = created_by
1130 notification.subject = subject
1143 notification.subject = subject
1131 notification.body = body
1144 notification.body = body
1132 notification.type_ = type_
1145 notification.type_ = type_
1133 notification.created_on = datetime.datetime.now()
1146 notification.created_on = datetime.datetime.now()
1134
1147
1135 for u in recipients:
1148 for u in recipients:
1136 assoc = UserNotification()
1149 assoc = UserNotification()
1137 assoc.notification = notification
1150 assoc.notification = notification
1138 u.notifications.append(assoc)
1151 u.notifications.append(assoc)
1139 Session.add(notification)
1152 Session.add(notification)
1140 return notification
1153 return notification
1141
1154
1142 @property
1155 @property
1143 def description(self):
1156 def description(self):
1144 from rhodecode.model.notification import NotificationModel
1157 from rhodecode.model.notification import NotificationModel
1145 return NotificationModel().make_description(self)
1158 return NotificationModel().make_description(self)
1146
1159
1147
1160
1148 class UserNotification(Base, BaseModel):
1161 class UserNotification(Base, BaseModel):
1149 __tablename__ = 'user_to_notification'
1162 __tablename__ = 'user_to_notification'
1150 __table_args__ = (
1163 __table_args__ = (
1151 UniqueConstraint('user_id', 'notification_id'),
1164 UniqueConstraint('user_id', 'notification_id'),
1152 {'extend_existing': True}
1165 {'extend_existing': True}
1153 )
1166 )
1154 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1167 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1155 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1168 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1156 read = Column('read', Boolean, default=False)
1169 read = Column('read', Boolean, default=False)
1157 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1170 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1158
1171
1159 user = relationship('User', lazy="joined")
1172 user = relationship('User', lazy="joined")
1160 notification = relationship('Notification', lazy="joined",
1173 notification = relationship('Notification', lazy="joined",
1161 order_by=lambda: Notification.created_on.desc(),)
1174 order_by=lambda: Notification.created_on.desc(),)
1162
1175
1163 def mark_as_read(self):
1176 def mark_as_read(self):
1164 self.read = True
1177 self.read = True
1165 Session.add(self)
1178 Session.add(self)
1166
1179
1167
1180
1168 class DbMigrateVersion(Base, BaseModel):
1181 class DbMigrateVersion(Base, BaseModel):
1169 __tablename__ = 'db_migrate_version'
1182 __tablename__ = 'db_migrate_version'
1170 __table_args__ = {'extend_existing': True}
1183 __table_args__ = {'extend_existing': True}
1171 repository_id = Column('repository_id', String(250), primary_key=True)
1184 repository_id = Column('repository_id', String(250), primary_key=True)
1172 repository_path = Column('repository_path', Text)
1185 repository_path = Column('repository_path', Text)
1173 version = Column('version', Integer)
1186 version = Column('version', Integer)
@@ -1,749 +1,758 b''
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import os
22 import os
23 import re
23 import re
24 import logging
24 import logging
25 import traceback
25 import traceback
26
26
27 import formencode
27 import formencode
28 from formencode import All
28 from formencode import All
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 Email, Bool, StringBoolean, Set
30 Email, Bool, StringBoolean, Set
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from webhelpers.pylonslib.secure_form import authentication_token
33 from webhelpers.pylonslib.secure_form import authentication_token
34
34
35 from rhodecode.config.routing import ADMIN_PREFIX
35 from rhodecode.config.routing import ADMIN_PREFIX
36 from rhodecode.lib.utils import repo_name_slug
36 from rhodecode.lib.utils import repo_name_slug
37 from rhodecode.lib.auth import authenticate, get_crypt_password
37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 from rhodecode.lib.exceptions import LdapImportError
38 from rhodecode.lib.exceptions import LdapImportError
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
40 from rhodecode import BACKENDS
40 from rhodecode import BACKENDS
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 #this is needed to translate the messages using _() in validators
45 #this is needed to translate the messages using _() in validators
46 class State_obj(object):
46 class State_obj(object):
47 _ = staticmethod(_)
47 _ = staticmethod(_)
48
48
49
49
50 #==============================================================================
50 #==============================================================================
51 # VALIDATORS
51 # VALIDATORS
52 #==============================================================================
52 #==============================================================================
53 class ValidAuthToken(formencode.validators.FancyValidator):
53 class ValidAuthToken(formencode.validators.FancyValidator):
54 messages = {'invalid_token': _('Token mismatch')}
54 messages = {'invalid_token': _('Token mismatch')}
55
55
56 def validate_python(self, value, state):
56 def validate_python(self, value, state):
57
57
58 if value != authentication_token():
58 if value != authentication_token():
59 raise formencode.Invalid(
59 raise formencode.Invalid(
60 self.message('invalid_token',
60 self.message('invalid_token',
61 state, search_number=value),
61 state, search_number=value),
62 value,
62 value,
63 state
63 state
64 )
64 )
65
65
66
66
67 def ValidUsername(edit, old_data):
67 def ValidUsername(edit, old_data):
68 class _ValidUsername(formencode.validators.FancyValidator):
68 class _ValidUsername(formencode.validators.FancyValidator):
69
69
70 def validate_python(self, value, state):
70 def validate_python(self, value, state):
71 if value in ['default', 'new_user']:
71 if value in ['default', 'new_user']:
72 raise formencode.Invalid(_('Invalid username'), value, state)
72 raise formencode.Invalid(_('Invalid username'), value, state)
73 #check if user is unique
73 #check if user is unique
74 old_un = None
74 old_un = None
75 if edit:
75 if edit:
76 old_un = User.get(old_data.get('user_id')).username
76 old_un = User.get(old_data.get('user_id')).username
77
77
78 if old_un != value or not edit:
78 if old_un != value or not edit:
79 if User.get_by_username(value, case_insensitive=True):
79 if User.get_by_username(value, case_insensitive=True):
80 raise formencode.Invalid(_('This username already '
80 raise formencode.Invalid(_('This username already '
81 'exists') , value, state)
81 'exists') , value, state)
82
82
83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
84 raise formencode.Invalid(
84 raise formencode.Invalid(
85 _('Username may only contain alphanumeric characters '
85 _('Username may only contain alphanumeric characters '
86 'underscores, periods or dashes and must begin with '
86 'underscores, periods or dashes and must begin with '
87 'alphanumeric character'),
87 'alphanumeric character'),
88 value,
88 value,
89 state
89 state
90 )
90 )
91
91
92 return _ValidUsername
92 return _ValidUsername
93
93
94
94
95 def ValidUsersGroup(edit, old_data):
95 def ValidUsersGroup(edit, old_data):
96
96
97 class _ValidUsersGroup(formencode.validators.FancyValidator):
97 class _ValidUsersGroup(formencode.validators.FancyValidator):
98
98
99 def validate_python(self, value, state):
99 def validate_python(self, value, state):
100 if value in ['default']:
100 if value in ['default']:
101 raise formencode.Invalid(_('Invalid group name'), value, state)
101 raise formencode.Invalid(_('Invalid group name'), value, state)
102 #check if group is unique
102 #check if group is unique
103 old_ugname = None
103 old_ugname = None
104 if edit:
104 if edit:
105 old_ugname = UsersGroup.get(
105 old_ugname = UsersGroup.get(
106 old_data.get('users_group_id')).users_group_name
106 old_data.get('users_group_id')).users_group_name
107
107
108 if old_ugname != value or not edit:
108 if old_ugname != value or not edit:
109 if UsersGroup.get_by_group_name(value, cache=False,
109 if UsersGroup.get_by_group_name(value, cache=False,
110 case_insensitive=True):
110 case_insensitive=True):
111 raise formencode.Invalid(_('This users group '
111 raise formencode.Invalid(_('This users group '
112 'already exists'), value,
112 'already exists'), value,
113 state)
113 state)
114
114
115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
116 raise formencode.Invalid(
116 raise formencode.Invalid(
117 _('RepoGroup name may only contain alphanumeric characters '
117 _('RepoGroup name may only contain alphanumeric characters '
118 'underscores, periods or dashes and must begin with '
118 'underscores, periods or dashes and must begin with '
119 'alphanumeric character'),
119 'alphanumeric character'),
120 value,
120 value,
121 state
121 state
122 )
122 )
123
123
124 return _ValidUsersGroup
124 return _ValidUsersGroup
125
125
126
126
127 def ValidReposGroup(edit, old_data):
127 def ValidReposGroup(edit, old_data):
128 class _ValidReposGroup(formencode.validators.FancyValidator):
128 class _ValidReposGroup(formencode.validators.FancyValidator):
129
129
130 def validate_python(self, value, state):
130 def validate_python(self, value, state):
131 # TODO WRITE VALIDATIONS
131 # TODO WRITE VALIDATIONS
132 group_name = value.get('group_name')
132 group_name = value.get('group_name')
133 group_parent_id = value.get('group_parent_id')
133 group_parent_id = value.get('group_parent_id')
134
134
135 # slugify repo group just in case :)
135 # slugify repo group just in case :)
136 slug = repo_name_slug(group_name)
136 slug = repo_name_slug(group_name)
137
137
138 # check for parent of self
138 # check for parent of self
139 parent_of_self = lambda: (
139 parent_of_self = lambda: (
140 old_data['group_id'] == int(group_parent_id)
140 old_data['group_id'] == int(group_parent_id)
141 if group_parent_id else False
141 if group_parent_id else False
142 )
142 )
143 if edit and parent_of_self():
143 if edit and parent_of_self():
144 e_dict = {
144 e_dict = {
145 'group_parent_id': _('Cannot assign this group as parent')
145 'group_parent_id': _('Cannot assign this group as parent')
146 }
146 }
147 raise formencode.Invalid('', value, state,
147 raise formencode.Invalid('', value, state,
148 error_dict=e_dict)
148 error_dict=e_dict)
149
149
150 old_gname = None
150 old_gname = None
151 if edit:
151 if edit:
152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
153
153
154 if old_gname != group_name or not edit:
154 if old_gname != group_name or not edit:
155
155
156 # check group
156 # check group
157 gr = RepoGroup.query()\
157 gr = RepoGroup.query()\
158 .filter(RepoGroup.group_name == slug)\
158 .filter(RepoGroup.group_name == slug)\
159 .filter(RepoGroup.group_parent_id == group_parent_id)\
159 .filter(RepoGroup.group_parent_id == group_parent_id)\
160 .scalar()
160 .scalar()
161
161
162 if gr:
162 if gr:
163 e_dict = {
163 e_dict = {
164 'group_name': _('This group already exists')
164 'group_name': _('This group already exists')
165 }
165 }
166 raise formencode.Invalid('', value, state,
166 raise formencode.Invalid('', value, state,
167 error_dict=e_dict)
167 error_dict=e_dict)
168
168
169 # check for same repo
169 # check for same repo
170 repo = Repository.query()\
170 repo = Repository.query()\
171 .filter(Repository.repo_name == slug)\
171 .filter(Repository.repo_name == slug)\
172 .scalar()
172 .scalar()
173
173
174 if repo:
174 if repo:
175 e_dict = {
175 e_dict = {
176 'group_name': _('Repository with this name already exists')
176 'group_name': _('Repository with this name already exists')
177 }
177 }
178 raise formencode.Invalid('', value, state,
178 raise formencode.Invalid('', value, state,
179 error_dict=e_dict)
179 error_dict=e_dict)
180
180
181 return _ValidReposGroup
181 return _ValidReposGroup
182
182
183
183
184 class ValidPassword(formencode.validators.FancyValidator):
184 class ValidPassword(formencode.validators.FancyValidator):
185
185
186 def to_python(self, value, state):
186 def to_python(self, value, state):
187
187
188 if not value:
188 if not value:
189 return
189 return
190
190
191 if value.get('password'):
191 if value.get('password'):
192 try:
192 try:
193 value['password'] = get_crypt_password(value['password'])
193 value['password'] = get_crypt_password(value['password'])
194 except UnicodeEncodeError:
194 except UnicodeEncodeError:
195 e_dict = {'password': _('Invalid characters in password')}
195 e_dict = {'password': _('Invalid characters in password')}
196 raise formencode.Invalid('', value, state, error_dict=e_dict)
196 raise formencode.Invalid('', value, state, error_dict=e_dict)
197
197
198 if value.get('password_confirmation'):
198 if value.get('password_confirmation'):
199 try:
199 try:
200 value['password_confirmation'] = \
200 value['password_confirmation'] = \
201 get_crypt_password(value['password_confirmation'])
201 get_crypt_password(value['password_confirmation'])
202 except UnicodeEncodeError:
202 except UnicodeEncodeError:
203 e_dict = {
203 e_dict = {
204 'password_confirmation': _('Invalid characters in password')
204 'password_confirmation': _('Invalid characters in password')
205 }
205 }
206 raise formencode.Invalid('', value, state, error_dict=e_dict)
206 raise formencode.Invalid('', value, state, error_dict=e_dict)
207
207
208 if value.get('new_password'):
208 if value.get('new_password'):
209 try:
209 try:
210 value['new_password'] = \
210 value['new_password'] = \
211 get_crypt_password(value['new_password'])
211 get_crypt_password(value['new_password'])
212 except UnicodeEncodeError:
212 except UnicodeEncodeError:
213 e_dict = {'new_password': _('Invalid characters in password')}
213 e_dict = {'new_password': _('Invalid characters in password')}
214 raise formencode.Invalid('', value, state, error_dict=e_dict)
214 raise formencode.Invalid('', value, state, error_dict=e_dict)
215
215
216 return value
216 return value
217
217
218
218
219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
220
220
221 def validate_python(self, value, state):
221 def validate_python(self, value, state):
222
222
223 pass_val = value.get('password') or value.get('new_password')
223 pass_val = value.get('password') or value.get('new_password')
224 if pass_val != value['password_confirmation']:
224 if pass_val != value['password_confirmation']:
225 e_dict = {'password_confirmation':
225 e_dict = {'password_confirmation':
226 _('Passwords do not match')}
226 _('Passwords do not match')}
227 raise formencode.Invalid('', value, state, error_dict=e_dict)
227 raise formencode.Invalid('', value, state, error_dict=e_dict)
228
228
229
229
230 class ValidAuth(formencode.validators.FancyValidator):
230 class ValidAuth(formencode.validators.FancyValidator):
231 messages = {
231 messages = {
232 'invalid_password':_('invalid password'),
232 'invalid_password':_('invalid password'),
233 'invalid_login':_('invalid user name'),
233 'invalid_login':_('invalid user name'),
234 'disabled_account':_('Your account is disabled')
234 'disabled_account':_('Your account is disabled')
235 }
235 }
236
236
237 # error mapping
237 # error mapping
238 e_dict = {'username': messages['invalid_login'],
238 e_dict = {'username': messages['invalid_login'],
239 'password': messages['invalid_password']}
239 'password': messages['invalid_password']}
240 e_dict_disable = {'username': messages['disabled_account']}
240 e_dict_disable = {'username': messages['disabled_account']}
241
241
242 def validate_python(self, value, state):
242 def validate_python(self, value, state):
243 password = value['password']
243 password = value['password']
244 username = value['username']
244 username = value['username']
245 user = User.get_by_username(username)
245 user = User.get_by_username(username)
246
246
247 if authenticate(username, password):
247 if authenticate(username, password):
248 return value
248 return value
249 else:
249 else:
250 if user and user.active is False:
250 if user and user.active is False:
251 log.warning('user %s is disabled' % username)
251 log.warning('user %s is disabled' % username)
252 raise formencode.Invalid(
252 raise formencode.Invalid(
253 self.message('disabled_account',
253 self.message('disabled_account',
254 state=State_obj),
254 state=State_obj),
255 value, state,
255 value, state,
256 error_dict=self.e_dict_disable
256 error_dict=self.e_dict_disable
257 )
257 )
258 else:
258 else:
259 log.warning('user %s not authenticated' % username)
259 log.warning('user %s not authenticated' % username)
260 raise formencode.Invalid(
260 raise formencode.Invalid(
261 self.message('invalid_password',
261 self.message('invalid_password',
262 state=State_obj), value, state,
262 state=State_obj), value, state,
263 error_dict=self.e_dict
263 error_dict=self.e_dict
264 )
264 )
265
265
266
266
267 class ValidRepoUser(formencode.validators.FancyValidator):
267 class ValidRepoUser(formencode.validators.FancyValidator):
268
268
269 def to_python(self, value, state):
269 def to_python(self, value, state):
270 try:
270 try:
271 User.query().filter(User.active == True)\
271 User.query().filter(User.active == True)\
272 .filter(User.username == value).one()
272 .filter(User.username == value).one()
273 except Exception:
273 except Exception:
274 raise formencode.Invalid(_('This username is not valid'),
274 raise formencode.Invalid(_('This username is not valid'),
275 value, state)
275 value, state)
276 return value
276 return value
277
277
278
278
279 def ValidRepoName(edit, old_data):
279 def ValidRepoName(edit, old_data):
280 class _ValidRepoName(formencode.validators.FancyValidator):
280 class _ValidRepoName(formencode.validators.FancyValidator):
281 def to_python(self, value, state):
281 def to_python(self, value, state):
282
282
283 repo_name = value.get('repo_name')
283 repo_name = value.get('repo_name')
284
284
285 slug = repo_name_slug(repo_name)
285 slug = repo_name_slug(repo_name)
286 if slug in [ADMIN_PREFIX, '']:
286 if slug in [ADMIN_PREFIX, '']:
287 e_dict = {'repo_name': _('This repository name is disallowed')}
287 e_dict = {'repo_name': _('This repository name is disallowed')}
288 raise formencode.Invalid('', value, state, error_dict=e_dict)
288 raise formencode.Invalid('', value, state, error_dict=e_dict)
289
289
290 if value.get('repo_group'):
290 if value.get('repo_group'):
291 gr = RepoGroup.get(value.get('repo_group'))
291 gr = RepoGroup.get(value.get('repo_group'))
292 group_path = gr.full_path
292 group_path = gr.full_path
293 # value needs to be aware of group name in order to check
293 # value needs to be aware of group name in order to check
294 # db key This is an actual just the name to store in the
294 # db key This is an actual just the name to store in the
295 # database
295 # database
296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
297
297
298 else:
298 else:
299 group_path = ''
299 group_path = ''
300 repo_name_full = repo_name
300 repo_name_full = repo_name
301
301
302 value['repo_name_full'] = repo_name_full
302 value['repo_name_full'] = repo_name_full
303 rename = old_data.get('repo_name') != repo_name_full
303 rename = old_data.get('repo_name') != repo_name_full
304 create = not edit
304 create = not edit
305 if rename or create:
305 if rename or create:
306
306
307 if group_path != '':
307 if group_path != '':
308 if Repository.get_by_repo_name(repo_name_full):
308 if Repository.get_by_repo_name(repo_name_full):
309 e_dict = {
309 e_dict = {
310 'repo_name': _('This repository already exists in '
310 'repo_name': _('This repository already exists in '
311 'a group "%s"') % gr.group_name
311 'a group "%s"') % gr.group_name
312 }
312 }
313 raise formencode.Invalid('', value, state,
313 raise formencode.Invalid('', value, state,
314 error_dict=e_dict)
314 error_dict=e_dict)
315 elif RepoGroup.get_by_group_name(repo_name_full):
315 elif RepoGroup.get_by_group_name(repo_name_full):
316 e_dict = {
316 e_dict = {
317 'repo_name': _('There is a group with this name '
317 'repo_name': _('There is a group with this name '
318 'already "%s"') % repo_name_full
318 'already "%s"') % repo_name_full
319 }
319 }
320 raise formencode.Invalid('', value, state,
320 raise formencode.Invalid('', value, state,
321 error_dict=e_dict)
321 error_dict=e_dict)
322
322
323 elif Repository.get_by_repo_name(repo_name_full):
323 elif Repository.get_by_repo_name(repo_name_full):
324 e_dict = {'repo_name': _('This repository '
324 e_dict = {'repo_name': _('This repository '
325 'already exists')}
325 'already exists')}
326 raise formencode.Invalid('', value, state,
326 raise formencode.Invalid('', value, state,
327 error_dict=e_dict)
327 error_dict=e_dict)
328
328
329 return value
329 return value
330
330
331 return _ValidRepoName
331 return _ValidRepoName
332
332
333
333
334 def ValidForkName(*args, **kwargs):
334 def ValidForkName(*args, **kwargs):
335 return ValidRepoName(*args, **kwargs)
335 return ValidRepoName(*args, **kwargs)
336
336
337
337
338 def SlugifyName():
338 def SlugifyName():
339 class _SlugifyName(formencode.validators.FancyValidator):
339 class _SlugifyName(formencode.validators.FancyValidator):
340
340
341 def to_python(self, value, state):
341 def to_python(self, value, state):
342 return repo_name_slug(value)
342 return repo_name_slug(value)
343
343
344 return _SlugifyName
344 return _SlugifyName
345
345
346
346
347 def ValidCloneUri():
347 def ValidCloneUri():
348 from mercurial.httprepo import httprepository, httpsrepository
348 from mercurial.httprepo import httprepository, httpsrepository
349 from rhodecode.lib.utils import make_ui
349 from rhodecode.lib.utils import make_ui
350
350
351 class _ValidCloneUri(formencode.validators.FancyValidator):
351 class _ValidCloneUri(formencode.validators.FancyValidator):
352
352
353 def to_python(self, value, state):
353 def to_python(self, value, state):
354 if not value:
354 if not value:
355 pass
355 pass
356 elif value.startswith('https'):
356 elif value.startswith('https'):
357 try:
357 try:
358 httpsrepository(make_ui('db'), value).capabilities
358 httpsrepository(make_ui('db'), value).capabilities
359 except Exception:
359 except Exception:
360 log.error(traceback.format_exc())
360 log.error(traceback.format_exc())
361 raise formencode.Invalid(_('invalid clone url'), value,
361 raise formencode.Invalid(_('invalid clone url'), value,
362 state)
362 state)
363 elif value.startswith('http'):
363 elif value.startswith('http'):
364 try:
364 try:
365 httprepository(make_ui('db'), value).capabilities
365 httprepository(make_ui('db'), value).capabilities
366 except Exception:
366 except Exception:
367 log.error(traceback.format_exc())
367 log.error(traceback.format_exc())
368 raise formencode.Invalid(_('invalid clone url'), value,
368 raise formencode.Invalid(_('invalid clone url'), value,
369 state)
369 state)
370 else:
370 else:
371 raise formencode.Invalid(_('Invalid clone url, provide a '
371 raise formencode.Invalid(_('Invalid clone url, provide a '
372 'valid clone http\s url'), value,
372 'valid clone http\s url'), value,
373 state)
373 state)
374 return value
374 return value
375
375
376 return _ValidCloneUri
376 return _ValidCloneUri
377
377
378
378
379 def ValidForkType(old_data):
379 def ValidForkType(old_data):
380 class _ValidForkType(formencode.validators.FancyValidator):
380 class _ValidForkType(formencode.validators.FancyValidator):
381
381
382 def to_python(self, value, state):
382 def to_python(self, value, state):
383 if old_data['repo_type'] != value:
383 if old_data['repo_type'] != value:
384 raise formencode.Invalid(_('Fork have to be the same '
384 raise formencode.Invalid(_('Fork have to be the same '
385 'type as original'), value, state)
385 'type as original'), value, state)
386
386
387 return value
387 return value
388 return _ValidForkType
388 return _ValidForkType
389
389
390
390
391 class ValidPerms(formencode.validators.FancyValidator):
391 def ValidPerms(type_='repo'):
392 messages = {'perm_new_member_name': _('This username or users group name'
392 if type_ == 'group':
393 ' is not valid')}
393 EMPTY_PERM = 'group.none'
394 elif type_ == 'repo':
395 EMPTY_PERM = 'repository.none'
394
396
395 def to_python(self, value, state):
397 class _ValidPerms(formencode.validators.FancyValidator):
396 perms_update = []
398 messages = {
397 perms_new = []
399 'perm_new_member_name':
398 #build a list of permission to update and new permission to create
400 _('This username or users group name is not valid')
399 for k, v in value.items():
401 }
400 #means new added member to permissions
402
401 if k.startswith('perm_new_member'):
403 def to_python(self, value, state):
402 new_perm = value.get('perm_new_member', False)
404 perms_update = []
403 new_member = value.get('perm_new_member_name', False)
405 perms_new = []
404 new_type = value.get('perm_new_member_type')
406 # build a list of permission to update and new permission to create
407 for k, v in value.items():
408 # means new added member to permissions
409 if k.startswith('perm_new_member'):
410 new_perm = value.get('perm_new_member', False)
411 new_member = value.get('perm_new_member_name', False)
412 new_type = value.get('perm_new_member_type')
405
413
406 if new_member and new_perm:
414 if new_member and new_perm:
407 if (new_member, new_perm, new_type) not in perms_new:
415 if (new_member, new_perm, new_type) not in perms_new:
408 perms_new.append((new_member, new_perm, new_type))
416 perms_new.append((new_member, new_perm, new_type))
409 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
417 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
410 member = k[7:]
418 member = k[7:]
411 t = {'u': 'user',
419 t = {'u': 'user',
412 'g': 'users_group'
420 'g': 'users_group'
413 }[k[0]]
421 }[k[0]]
414 if member == 'default':
422 if member == 'default':
415 if value['private']:
423 if value.get('private'):
416 #set none for default when updating to private repo
424 # set none for default when updating to private repo
417 v = 'repository.none'
425 v = EMPTY_PERM
418 perms_update.append((member, v, t))
426 perms_update.append((member, v, t))
419
427
420 value['perms_updates'] = perms_update
428 value['perms_updates'] = perms_update
421 value['perms_new'] = perms_new
429 value['perms_new'] = perms_new
422
430
423 #update permissions
431 # update permissions
424 for k, v, t in perms_new:
432 for k, v, t in perms_new:
425 try:
433 try:
426 if t is 'user':
434 if t is 'user':
427 self.user_db = User.query()\
435 self.user_db = User.query()\
428 .filter(User.active == True)\
436 .filter(User.active == True)\
429 .filter(User.username == k).one()
437 .filter(User.username == k).one()
430 if t is 'users_group':
438 if t is 'users_group':
431 self.user_db = UsersGroup.query()\
439 self.user_db = UsersGroup.query()\
432 .filter(UsersGroup.users_group_active == True)\
440 .filter(UsersGroup.users_group_active == True)\
433 .filter(UsersGroup.users_group_name == k).one()
441 .filter(UsersGroup.users_group_name == k).one()
434
442
435 except Exception:
443 except Exception:
436 msg = self.message('perm_new_member_name',
444 msg = self.message('perm_new_member_name',
437 state=State_obj)
445 state=State_obj)
438 raise formencode.Invalid(
446 raise formencode.Invalid(
439 msg, value, state, error_dict={'perm_new_member_name': msg}
447 msg, value, state, error_dict={'perm_new_member_name': msg}
440 )
448 )
441 return value
449 return value
450 return _ValidPerms
442
451
443
452
444 class ValidSettings(formencode.validators.FancyValidator):
453 class ValidSettings(formencode.validators.FancyValidator):
445
454
446 def to_python(self, value, state):
455 def to_python(self, value, state):
447 # settings form can't edit user
456 # settings form can't edit user
448 if 'user' in value:
457 if 'user' in value:
449 del['value']['user']
458 del['value']['user']
450 return value
459 return value
451
460
452
461
453 class ValidPath(formencode.validators.FancyValidator):
462 class ValidPath(formencode.validators.FancyValidator):
454 def to_python(self, value, state):
463 def to_python(self, value, state):
455
464
456 if not os.path.isdir(value):
465 if not os.path.isdir(value):
457 msg = _('This is not a valid path')
466 msg = _('This is not a valid path')
458 raise formencode.Invalid(msg, value, state,
467 raise formencode.Invalid(msg, value, state,
459 error_dict={'paths_root_path': msg})
468 error_dict={'paths_root_path': msg})
460 return value
469 return value
461
470
462
471
463 def UniqSystemEmail(old_data):
472 def UniqSystemEmail(old_data):
464 class _UniqSystemEmail(formencode.validators.FancyValidator):
473 class _UniqSystemEmail(formencode.validators.FancyValidator):
465 def to_python(self, value, state):
474 def to_python(self, value, state):
466 value = value.lower()
475 value = value.lower()
467 if old_data.get('email', '').lower() != value:
476 if old_data.get('email', '').lower() != value:
468 user = User.get_by_email(value, case_insensitive=True)
477 user = User.get_by_email(value, case_insensitive=True)
469 if user:
478 if user:
470 raise formencode.Invalid(
479 raise formencode.Invalid(
471 _("This e-mail address is already taken"), value, state
480 _("This e-mail address is already taken"), value, state
472 )
481 )
473 return value
482 return value
474
483
475 return _UniqSystemEmail
484 return _UniqSystemEmail
476
485
477
486
478 class ValidSystemEmail(formencode.validators.FancyValidator):
487 class ValidSystemEmail(formencode.validators.FancyValidator):
479 def to_python(self, value, state):
488 def to_python(self, value, state):
480 value = value.lower()
489 value = value.lower()
481 user = User.get_by_email(value, case_insensitive=True)
490 user = User.get_by_email(value, case_insensitive=True)
482 if user is None:
491 if user is None:
483 raise formencode.Invalid(
492 raise formencode.Invalid(
484 _("This e-mail address doesn't exist."), value, state
493 _("This e-mail address doesn't exist."), value, state
485 )
494 )
486
495
487 return value
496 return value
488
497
489
498
490 class LdapLibValidator(formencode.validators.FancyValidator):
499 class LdapLibValidator(formencode.validators.FancyValidator):
491
500
492 def to_python(self, value, state):
501 def to_python(self, value, state):
493
502
494 try:
503 try:
495 import ldap
504 import ldap
496 except ImportError:
505 except ImportError:
497 raise LdapImportError
506 raise LdapImportError
498 return value
507 return value
499
508
500
509
501 class AttrLoginValidator(formencode.validators.FancyValidator):
510 class AttrLoginValidator(formencode.validators.FancyValidator):
502
511
503 def to_python(self, value, state):
512 def to_python(self, value, state):
504
513
505 if not value or not isinstance(value, (str, unicode)):
514 if not value or not isinstance(value, (str, unicode)):
506 raise formencode.Invalid(
515 raise formencode.Invalid(
507 _("The LDAP Login attribute of the CN must be specified - "
516 _("The LDAP Login attribute of the CN must be specified - "
508 "this is the name of the attribute that is equivalent "
517 "this is the name of the attribute that is equivalent "
509 "to 'username'"), value, state
518 "to 'username'"), value, state
510 )
519 )
511
520
512 return value
521 return value
513
522
514
523
515 #==============================================================================
524 #==============================================================================
516 # FORMS
525 # FORMS
517 #==============================================================================
526 #==============================================================================
518 class LoginForm(formencode.Schema):
527 class LoginForm(formencode.Schema):
519 allow_extra_fields = True
528 allow_extra_fields = True
520 filter_extra_fields = True
529 filter_extra_fields = True
521 username = UnicodeString(
530 username = UnicodeString(
522 strip=True,
531 strip=True,
523 min=1,
532 min=1,
524 not_empty=True,
533 not_empty=True,
525 messages={
534 messages={
526 'empty': _('Please enter a login'),
535 'empty': _('Please enter a login'),
527 'tooShort': _('Enter a value %(min)i characters long or more')}
536 'tooShort': _('Enter a value %(min)i characters long or more')}
528 )
537 )
529
538
530 password = UnicodeString(
539 password = UnicodeString(
531 strip=True,
540 strip=True,
532 min=3,
541 min=3,
533 not_empty=True,
542 not_empty=True,
534 messages={
543 messages={
535 'empty': _('Please enter a password'),
544 'empty': _('Please enter a password'),
536 'tooShort': _('Enter %(min)i characters or more')}
545 'tooShort': _('Enter %(min)i characters or more')}
537 )
546 )
538
547
539 remember = StringBoolean(if_missing=False)
548 remember = StringBoolean(if_missing=False)
540
549
541 chained_validators = [ValidAuth]
550 chained_validators = [ValidAuth]
542
551
543
552
544 def UserForm(edit=False, old_data={}):
553 def UserForm(edit=False, old_data={}):
545 class _UserForm(formencode.Schema):
554 class _UserForm(formencode.Schema):
546 allow_extra_fields = True
555 allow_extra_fields = True
547 filter_extra_fields = True
556 filter_extra_fields = True
548 username = All(UnicodeString(strip=True, min=1, not_empty=True),
557 username = All(UnicodeString(strip=True, min=1, not_empty=True),
549 ValidUsername(edit, old_data))
558 ValidUsername(edit, old_data))
550 if edit:
559 if edit:
551 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
560 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
552 password_confirmation = All(UnicodeString(strip=True, min=6,
561 password_confirmation = All(UnicodeString(strip=True, min=6,
553 not_empty=False))
562 not_empty=False))
554 admin = StringBoolean(if_missing=False)
563 admin = StringBoolean(if_missing=False)
555 else:
564 else:
556 password = All(UnicodeString(strip=True, min=6, not_empty=True))
565 password = All(UnicodeString(strip=True, min=6, not_empty=True))
557 password_confirmation = All(UnicodeString(strip=True, min=6,
566 password_confirmation = All(UnicodeString(strip=True, min=6,
558 not_empty=False))
567 not_empty=False))
559
568
560 active = StringBoolean(if_missing=False)
569 active = StringBoolean(if_missing=False)
561 name = UnicodeString(strip=True, min=1, not_empty=False)
570 name = UnicodeString(strip=True, min=1, not_empty=False)
562 lastname = UnicodeString(strip=True, min=1, not_empty=False)
571 lastname = UnicodeString(strip=True, min=1, not_empty=False)
563 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
572 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
564
573
565 chained_validators = [ValidPasswordsMatch, ValidPassword]
574 chained_validators = [ValidPasswordsMatch, ValidPassword]
566
575
567 return _UserForm
576 return _UserForm
568
577
569
578
570 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
579 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
571 class _UsersGroupForm(formencode.Schema):
580 class _UsersGroupForm(formencode.Schema):
572 allow_extra_fields = True
581 allow_extra_fields = True
573 filter_extra_fields = True
582 filter_extra_fields = True
574
583
575 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
584 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
576 ValidUsersGroup(edit, old_data))
585 ValidUsersGroup(edit, old_data))
577
586
578 users_group_active = StringBoolean(if_missing=False)
587 users_group_active = StringBoolean(if_missing=False)
579
588
580 if edit:
589 if edit:
581 users_group_members = OneOf(available_members, hideList=False,
590 users_group_members = OneOf(available_members, hideList=False,
582 testValueList=True,
591 testValueList=True,
583 if_missing=None, not_empty=False)
592 if_missing=None, not_empty=False)
584
593
585 return _UsersGroupForm
594 return _UsersGroupForm
586
595
587
596
588 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
597 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
589 class _ReposGroupForm(formencode.Schema):
598 class _ReposGroupForm(formencode.Schema):
590 allow_extra_fields = True
599 allow_extra_fields = True
591 filter_extra_fields = True
600 filter_extra_fields = False
592
601
593 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
602 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
594 SlugifyName())
603 SlugifyName())
595 group_description = UnicodeString(strip=True, min=1,
604 group_description = UnicodeString(strip=True, min=1,
596 not_empty=True)
605 not_empty=True)
597 group_parent_id = OneOf(available_groups, hideList=False,
606 group_parent_id = OneOf(available_groups, hideList=False,
598 testValueList=True,
607 testValueList=True,
599 if_missing=None, not_empty=False)
608 if_missing=None, not_empty=False)
600
609
601 chained_validators = [ValidReposGroup(edit, old_data)]
610 chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')]
602
611
603 return _ReposGroupForm
612 return _ReposGroupForm
604
613
605
614
606 def RegisterForm(edit=False, old_data={}):
615 def RegisterForm(edit=False, old_data={}):
607 class _RegisterForm(formencode.Schema):
616 class _RegisterForm(formencode.Schema):
608 allow_extra_fields = True
617 allow_extra_fields = True
609 filter_extra_fields = True
618 filter_extra_fields = True
610 username = All(ValidUsername(edit, old_data),
619 username = All(ValidUsername(edit, old_data),
611 UnicodeString(strip=True, min=1, not_empty=True))
620 UnicodeString(strip=True, min=1, not_empty=True))
612 password = All(UnicodeString(strip=True, min=6, not_empty=True))
621 password = All(UnicodeString(strip=True, min=6, not_empty=True))
613 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
622 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
614 active = StringBoolean(if_missing=False)
623 active = StringBoolean(if_missing=False)
615 name = UnicodeString(strip=True, min=1, not_empty=False)
624 name = UnicodeString(strip=True, min=1, not_empty=False)
616 lastname = UnicodeString(strip=True, min=1, not_empty=False)
625 lastname = UnicodeString(strip=True, min=1, not_empty=False)
617 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
626 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
618
627
619 chained_validators = [ValidPasswordsMatch, ValidPassword]
628 chained_validators = [ValidPasswordsMatch, ValidPassword]
620
629
621 return _RegisterForm
630 return _RegisterForm
622
631
623
632
624 def PasswordResetForm():
633 def PasswordResetForm():
625 class _PasswordResetForm(formencode.Schema):
634 class _PasswordResetForm(formencode.Schema):
626 allow_extra_fields = True
635 allow_extra_fields = True
627 filter_extra_fields = True
636 filter_extra_fields = True
628 email = All(ValidSystemEmail(), Email(not_empty=True))
637 email = All(ValidSystemEmail(), Email(not_empty=True))
629 return _PasswordResetForm
638 return _PasswordResetForm
630
639
631
640
632 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
641 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
633 repo_groups=[]):
642 repo_groups=[]):
634 class _RepoForm(formencode.Schema):
643 class _RepoForm(formencode.Schema):
635 allow_extra_fields = True
644 allow_extra_fields = True
636 filter_extra_fields = False
645 filter_extra_fields = False
637 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
646 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
638 SlugifyName())
647 SlugifyName())
639 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
648 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
640 ValidCloneUri()())
649 ValidCloneUri()())
641 repo_group = OneOf(repo_groups, hideList=True)
650 repo_group = OneOf(repo_groups, hideList=True)
642 repo_type = OneOf(supported_backends)
651 repo_type = OneOf(supported_backends)
643 description = UnicodeString(strip=True, min=1, not_empty=True)
652 description = UnicodeString(strip=True, min=1, not_empty=True)
644 private = StringBoolean(if_missing=False)
653 private = StringBoolean(if_missing=False)
645 enable_statistics = StringBoolean(if_missing=False)
654 enable_statistics = StringBoolean(if_missing=False)
646 enable_downloads = StringBoolean(if_missing=False)
655 enable_downloads = StringBoolean(if_missing=False)
647
656
648 if edit:
657 if edit:
649 #this is repo owner
658 #this is repo owner
650 user = All(UnicodeString(not_empty=True), ValidRepoUser)
659 user = All(UnicodeString(not_empty=True), ValidRepoUser)
651
660
652 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
661 chained_validators = [ValidRepoName(edit, old_data), ValidPerms()]
653 return _RepoForm
662 return _RepoForm
654
663
655
664
656 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
665 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
657 repo_groups=[]):
666 repo_groups=[]):
658 class _RepoForkForm(formencode.Schema):
667 class _RepoForkForm(formencode.Schema):
659 allow_extra_fields = True
668 allow_extra_fields = True
660 filter_extra_fields = False
669 filter_extra_fields = False
661 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
670 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
662 SlugifyName())
671 SlugifyName())
663 repo_group = OneOf(repo_groups, hideList=True)
672 repo_group = OneOf(repo_groups, hideList=True)
664 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
673 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
665 description = UnicodeString(strip=True, min=1, not_empty=True)
674 description = UnicodeString(strip=True, min=1, not_empty=True)
666 private = StringBoolean(if_missing=False)
675 private = StringBoolean(if_missing=False)
667 copy_permissions = StringBoolean(if_missing=False)
676 copy_permissions = StringBoolean(if_missing=False)
668 update_after_clone = StringBoolean(if_missing=False)
677 update_after_clone = StringBoolean(if_missing=False)
669 fork_parent_id = UnicodeString()
678 fork_parent_id = UnicodeString()
670 chained_validators = [ValidForkName(edit, old_data)]
679 chained_validators = [ValidForkName(edit, old_data)]
671
680
672 return _RepoForkForm
681 return _RepoForkForm
673
682
674
683
675 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
684 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
676 repo_groups=[]):
685 repo_groups=[]):
677 class _RepoForm(formencode.Schema):
686 class _RepoForm(formencode.Schema):
678 allow_extra_fields = True
687 allow_extra_fields = True
679 filter_extra_fields = False
688 filter_extra_fields = False
680 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
689 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
681 SlugifyName())
690 SlugifyName())
682 description = UnicodeString(strip=True, min=1, not_empty=True)
691 description = UnicodeString(strip=True, min=1, not_empty=True)
683 repo_group = OneOf(repo_groups, hideList=True)
692 repo_group = OneOf(repo_groups, hideList=True)
684 private = StringBoolean(if_missing=False)
693 private = StringBoolean(if_missing=False)
685
694
686 chained_validators = [ValidRepoName(edit, old_data), ValidPerms,
695 chained_validators = [ValidRepoName(edit, old_data), ValidPerms(),
687 ValidSettings]
696 ValidSettings]
688 return _RepoForm
697 return _RepoForm
689
698
690
699
691 def ApplicationSettingsForm():
700 def ApplicationSettingsForm():
692 class _ApplicationSettingsForm(formencode.Schema):
701 class _ApplicationSettingsForm(formencode.Schema):
693 allow_extra_fields = True
702 allow_extra_fields = True
694 filter_extra_fields = False
703 filter_extra_fields = False
695 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
704 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
696 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
705 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
697 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
706 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
698
707
699 return _ApplicationSettingsForm
708 return _ApplicationSettingsForm
700
709
701
710
702 def ApplicationUiSettingsForm():
711 def ApplicationUiSettingsForm():
703 class _ApplicationUiSettingsForm(formencode.Schema):
712 class _ApplicationUiSettingsForm(formencode.Schema):
704 allow_extra_fields = True
713 allow_extra_fields = True
705 filter_extra_fields = False
714 filter_extra_fields = False
706 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
715 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
707 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
716 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
708 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
717 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
709 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
718 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
710 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
719 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
711 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
720 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
712
721
713 return _ApplicationUiSettingsForm
722 return _ApplicationUiSettingsForm
714
723
715
724
716 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
725 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
717 class _DefaultPermissionsForm(formencode.Schema):
726 class _DefaultPermissionsForm(formencode.Schema):
718 allow_extra_fields = True
727 allow_extra_fields = True
719 filter_extra_fields = True
728 filter_extra_fields = True
720 overwrite_default = StringBoolean(if_missing=False)
729 overwrite_default = StringBoolean(if_missing=False)
721 anonymous = OneOf(['True', 'False'], if_missing=False)
730 anonymous = OneOf(['True', 'False'], if_missing=False)
722 default_perm = OneOf(perms_choices)
731 default_perm = OneOf(perms_choices)
723 default_register = OneOf(register_choices)
732 default_register = OneOf(register_choices)
724 default_create = OneOf(create_choices)
733 default_create = OneOf(create_choices)
725
734
726 return _DefaultPermissionsForm
735 return _DefaultPermissionsForm
727
736
728
737
729 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
738 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
730 class _LdapSettingsForm(formencode.Schema):
739 class _LdapSettingsForm(formencode.Schema):
731 allow_extra_fields = True
740 allow_extra_fields = True
732 filter_extra_fields = True
741 filter_extra_fields = True
733 pre_validators = [LdapLibValidator]
742 pre_validators = [LdapLibValidator]
734 ldap_active = StringBoolean(if_missing=False)
743 ldap_active = StringBoolean(if_missing=False)
735 ldap_host = UnicodeString(strip=True,)
744 ldap_host = UnicodeString(strip=True,)
736 ldap_port = Number(strip=True,)
745 ldap_port = Number(strip=True,)
737 ldap_tls_kind = OneOf(tls_kind_choices)
746 ldap_tls_kind = OneOf(tls_kind_choices)
738 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
747 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
739 ldap_dn_user = UnicodeString(strip=True,)
748 ldap_dn_user = UnicodeString(strip=True,)
740 ldap_dn_pass = UnicodeString(strip=True,)
749 ldap_dn_pass = UnicodeString(strip=True,)
741 ldap_base_dn = UnicodeString(strip=True,)
750 ldap_base_dn = UnicodeString(strip=True,)
742 ldap_filter = UnicodeString(strip=True,)
751 ldap_filter = UnicodeString(strip=True,)
743 ldap_search_scope = OneOf(search_scope_choices)
752 ldap_search_scope = OneOf(search_scope_choices)
744 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
753 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
745 ldap_attr_firstname = UnicodeString(strip=True,)
754 ldap_attr_firstname = UnicodeString(strip=True,)
746 ldap_attr_lastname = UnicodeString(strip=True,)
755 ldap_attr_lastname = UnicodeString(strip=True,)
747 ldap_attr_email = UnicodeString(strip=True,)
756 ldap_attr_email = UnicodeString(strip=True,)
748
757
749 return _LdapSettingsForm
758 return _LdapSettingsForm
@@ -1,219 +1,216 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.notification
3 rhodecode.model.notification
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Model for notifications
6 Model for notifications
7
7
8
8
9 :created_on: Nov 20, 2011
9 :created_on: Nov 20, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import datetime
30 import datetime
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 import rhodecode
34 import rhodecode
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.model import BaseModel
36 from rhodecode.model import BaseModel
37 from rhodecode.model.db import Notification, User, UserNotification
37 from rhodecode.model.db import Notification, User, UserNotification
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class NotificationModel(BaseModel):
42 class NotificationModel(BaseModel):
43
43
44 def __get_user(self, user):
44 def __get_user(self, user):
45 if isinstance(user, basestring):
45 return self._get_instance(User, user, callback=User.get_by_username)
46 return User.get_by_username(username=user)
47 else:
48 return self._get_instance(User, user)
49
46
50 def __get_notification(self, notification):
47 def __get_notification(self, notification):
51 if isinstance(notification, Notification):
48 if isinstance(notification, Notification):
52 return notification
49 return notification
53 elif isinstance(notification, int):
50 elif isinstance(notification, int):
54 return Notification.get(notification)
51 return Notification.get(notification)
55 else:
52 else:
56 if notification:
53 if notification:
57 raise Exception('notification must be int or Instance'
54 raise Exception('notification must be int or Instance'
58 ' of Notification got %s' % type(notification))
55 ' of Notification got %s' % type(notification))
59
56
60 def create(self, created_by, subject, body, recipients=None,
57 def create(self, created_by, subject, body, recipients=None,
61 type_=Notification.TYPE_MESSAGE, with_email=True,
58 type_=Notification.TYPE_MESSAGE, with_email=True,
62 email_kwargs={}):
59 email_kwargs={}):
63 """
60 """
64
61
65 Creates notification of given type
62 Creates notification of given type
66
63
67 :param created_by: int, str or User instance. User who created this
64 :param created_by: int, str or User instance. User who created this
68 notification
65 notification
69 :param subject:
66 :param subject:
70 :param body:
67 :param body:
71 :param recipients: list of int, str or User objects, when None
68 :param recipients: list of int, str or User objects, when None
72 is given send to all admins
69 is given send to all admins
73 :param type_: type of notification
70 :param type_: type of notification
74 :param with_email: send email with this notification
71 :param with_email: send email with this notification
75 :param email_kwargs: additional dict to pass as args to email template
72 :param email_kwargs: additional dict to pass as args to email template
76 """
73 """
77 from rhodecode.lib.celerylib import tasks, run_task
74 from rhodecode.lib.celerylib import tasks, run_task
78
75
79 if recipients and not getattr(recipients, '__iter__', False):
76 if recipients and not getattr(recipients, '__iter__', False):
80 raise Exception('recipients must be a list of iterable')
77 raise Exception('recipients must be a list of iterable')
81
78
82 created_by_obj = self.__get_user(created_by)
79 created_by_obj = self.__get_user(created_by)
83
80
84 if recipients:
81 if recipients:
85 recipients_objs = []
82 recipients_objs = []
86 for u in recipients:
83 for u in recipients:
87 obj = self.__get_user(u)
84 obj = self.__get_user(u)
88 if obj:
85 if obj:
89 recipients_objs.append(obj)
86 recipients_objs.append(obj)
90 recipients_objs = set(recipients_objs)
87 recipients_objs = set(recipients_objs)
91 else:
88 else:
92 # empty recipients means to all admins
89 # empty recipients means to all admins
93 recipients_objs = User.query().filter(User.admin == True).all()
90 recipients_objs = User.query().filter(User.admin == True).all()
94
91
95 notif = Notification.create(created_by=created_by_obj, subject=subject,
92 notif = Notification.create(created_by=created_by_obj, subject=subject,
96 body=body, recipients=recipients_objs,
93 body=body, recipients=recipients_objs,
97 type_=type_)
94 type_=type_)
98
95
99 if with_email is False:
96 if with_email is False:
100 return notif
97 return notif
101
98
102 # send email with notification
99 # send email with notification
103 for rec in recipients_objs:
100 for rec in recipients_objs:
104 email_subject = NotificationModel().make_description(notif, False)
101 email_subject = NotificationModel().make_description(notif, False)
105 type_ = type_
102 type_ = type_
106 email_body = body
103 email_body = body
107 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
104 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
108 kwargs.update(email_kwargs)
105 kwargs.update(email_kwargs)
109 email_body_html = EmailNotificationModel()\
106 email_body_html = EmailNotificationModel()\
110 .get_email_tmpl(type_, **kwargs)
107 .get_email_tmpl(type_, **kwargs)
111 run_task(tasks.send_email, rec.email, email_subject, email_body,
108 run_task(tasks.send_email, rec.email, email_subject, email_body,
112 email_body_html)
109 email_body_html)
113
110
114 return notif
111 return notif
115
112
116 def delete(self, user, notification):
113 def delete(self, user, notification):
117 # we don't want to remove actual notification just the assignment
114 # we don't want to remove actual notification just the assignment
118 try:
115 try:
119 notification = self.__get_notification(notification)
116 notification = self.__get_notification(notification)
120 user = self.__get_user(user)
117 user = self.__get_user(user)
121 if notification and user:
118 if notification and user:
122 obj = UserNotification.query()\
119 obj = UserNotification.query()\
123 .filter(UserNotification.user == user)\
120 .filter(UserNotification.user == user)\
124 .filter(UserNotification.notification
121 .filter(UserNotification.notification
125 == notification)\
122 == notification)\
126 .one()
123 .one()
127 self.sa.delete(obj)
124 self.sa.delete(obj)
128 return True
125 return True
129 except Exception:
126 except Exception:
130 log.error(traceback.format_exc())
127 log.error(traceback.format_exc())
131 raise
128 raise
132
129
133 def get_for_user(self, user):
130 def get_for_user(self, user):
134 user = self.__get_user(user)
131 user = self.__get_user(user)
135 return user.notifications
132 return user.notifications
136
133
137 def mark_all_read_for_user(self, user):
134 def mark_all_read_for_user(self, user):
138 user = self.__get_user(user)
135 user = self.__get_user(user)
139 UserNotification.query()\
136 UserNotification.query()\
140 .filter(UserNotification.read==False)\
137 .filter(UserNotification.read==False)\
141 .update({'read': True})
138 .update({'read': True})
142
139
143 def get_unread_cnt_for_user(self, user):
140 def get_unread_cnt_for_user(self, user):
144 user = self.__get_user(user)
141 user = self.__get_user(user)
145 return UserNotification.query()\
142 return UserNotification.query()\
146 .filter(UserNotification.read == False)\
143 .filter(UserNotification.read == False)\
147 .filter(UserNotification.user == user).count()
144 .filter(UserNotification.user == user).count()
148
145
149 def get_unread_for_user(self, user):
146 def get_unread_for_user(self, user):
150 user = self.__get_user(user)
147 user = self.__get_user(user)
151 return [x.notification for x in UserNotification.query()\
148 return [x.notification for x in UserNotification.query()\
152 .filter(UserNotification.read == False)\
149 .filter(UserNotification.read == False)\
153 .filter(UserNotification.user == user).all()]
150 .filter(UserNotification.user == user).all()]
154
151
155 def get_user_notification(self, user, notification):
152 def get_user_notification(self, user, notification):
156 user = self.__get_user(user)
153 user = self.__get_user(user)
157 notification = self.__get_notification(notification)
154 notification = self.__get_notification(notification)
158
155
159 return UserNotification.query()\
156 return UserNotification.query()\
160 .filter(UserNotification.notification == notification)\
157 .filter(UserNotification.notification == notification)\
161 .filter(UserNotification.user == user).scalar()
158 .filter(UserNotification.user == user).scalar()
162
159
163 def make_description(self, notification, show_age=True):
160 def make_description(self, notification, show_age=True):
164 """
161 """
165 Creates a human readable description based on properties
162 Creates a human readable description based on properties
166 of notification object
163 of notification object
167 """
164 """
168
165
169 _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
166 _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
170 notification.TYPE_MESSAGE:_('sent message'),
167 notification.TYPE_MESSAGE:_('sent message'),
171 notification.TYPE_MENTION:_('mentioned you'),
168 notification.TYPE_MENTION:_('mentioned you'),
172 notification.TYPE_REGISTRATION:_('registered in RhodeCode')}
169 notification.TYPE_REGISTRATION:_('registered in RhodeCode')}
173
170
174 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
171 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
175
172
176 tmpl = "%(user)s %(action)s %(when)s"
173 tmpl = "%(user)s %(action)s %(when)s"
177 if show_age:
174 if show_age:
178 when = h.age(notification.created_on)
175 when = h.age(notification.created_on)
179 else:
176 else:
180 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
177 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
181 when = DTF(notification.created_on)
178 when = DTF(notification.created_on)
182 data = dict(user=notification.created_by_user.username,
179 data = dict(user=notification.created_by_user.username,
183 action=_map[notification.type_],
180 action=_map[notification.type_],
184 when=when)
181 when=when)
185 return tmpl % data
182 return tmpl % data
186
183
187
184
188 class EmailNotificationModel(BaseModel):
185 class EmailNotificationModel(BaseModel):
189
186
190 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
187 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
191 TYPE_PASSWORD_RESET = 'passoword_link'
188 TYPE_PASSWORD_RESET = 'passoword_link'
192 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
189 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
193 TYPE_DEFAULT = 'default'
190 TYPE_DEFAULT = 'default'
194
191
195 def __init__(self):
192 def __init__(self):
196 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
193 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
197 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
194 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
198
195
199 self.email_types = {
196 self.email_types = {
200 self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
197 self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
201 self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
198 self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
202 self.TYPE_REGISTRATION:'email_templates/registration.html',
199 self.TYPE_REGISTRATION:'email_templates/registration.html',
203 self.TYPE_DEFAULT:'email_templates/default.html'
200 self.TYPE_DEFAULT:'email_templates/default.html'
204 }
201 }
205
202
206 def get_email_tmpl(self, type_, **kwargs):
203 def get_email_tmpl(self, type_, **kwargs):
207 """
204 """
208 return generated template for email based on given type
205 return generated template for email based on given type
209
206
210 :param type_:
207 :param type_:
211 """
208 """
212
209
213 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
210 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
214 email_template = self._tmpl_lookup.get_template(base)
211 email_template = self._tmpl_lookup.get_template(base)
215 # translator inject
212 # translator inject
216 _kwargs = {'_':_}
213 _kwargs = {'_':_}
217 _kwargs.update(kwargs)
214 _kwargs.update(kwargs)
218 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
215 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
219 return email_template.render(**_kwargs)
216 return email_template.render(**_kwargs)
@@ -1,432 +1,491 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.repo
3 rhodecode.model.repo
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository model for rhodecode
6 Repository model for rhodecode
7
7
8 :created_on: Jun 5, 2010
8 :created_on: Jun 5, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import shutil
26 import shutil
27 import logging
27 import logging
28 import traceback
28 import traceback
29 from datetime import datetime
29 from datetime import datetime
30
30
31 from vcs.utils.lazy import LazyProperty
32 from vcs.backends import get_backend
31 from vcs.backends import get_backend
33
32
33 from rhodecode.lib import LazyProperty
34 from rhodecode.lib import safe_str, safe_unicode
34 from rhodecode.lib import safe_str, safe_unicode
35 from rhodecode.lib.caching_query import FromCache
35 from rhodecode.lib.caching_query import FromCache
36 from rhodecode.lib.hooks import log_create_repository
36 from rhodecode.lib.hooks import log_create_repository
37
37
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
39 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
40 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
40 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
41
41
42
42 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
43
44
44
45
45 class RepoModel(BaseModel):
46 class RepoModel(BaseModel):
46
47
48 def __get_user(self, user):
49 return self._get_instance(User, user, callback=User.get_by_username)
50
51 def __get_users_group(self, users_group):
52 return self._get_instance(UsersGroup, users_group,
53 callback=UsersGroup.get_by_group_name)
54
55 def __get_repos_group(self, repos_group):
56 return self._get_instance(RepoGroup, repos_group,
57 callback=RepoGroup.get_by_group_name)
58
59 def __get_repo(self, repository):
60 return self._get_instance(Repository, repository,
61 callback=Repository.get_by_repo_name)
62
63 def __get_perm(self, permission):
64 return self._get_instance(Permission, permission,
65 callback=Permission.get_by_key)
66
47 @LazyProperty
67 @LazyProperty
48 def repos_path(self):
68 def repos_path(self):
49 """
69 """
50 Get's the repositories root path from database
70 Get's the repositories root path from database
51 """
71 """
52
72
53 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
73 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
54 return q.ui_value
74 return q.ui_value
55
75
56 def get(self, repo_id, cache=False):
76 def get(self, repo_id, cache=False):
57 repo = self.sa.query(Repository)\
77 repo = self.sa.query(Repository)\
58 .filter(Repository.repo_id == repo_id)
78 .filter(Repository.repo_id == repo_id)
59
79
60 if cache:
80 if cache:
61 repo = repo.options(FromCache("sql_cache_short",
81 repo = repo.options(FromCache("sql_cache_short",
62 "get_repo_%s" % repo_id))
82 "get_repo_%s" % repo_id))
63 return repo.scalar()
83 return repo.scalar()
64
84
65 def get_by_repo_name(self, repo_name, cache=False):
85 def get_by_repo_name(self, repo_name, cache=False):
66 repo = self.sa.query(Repository)\
86 repo = self.sa.query(Repository)\
67 .filter(Repository.repo_name == repo_name)
87 .filter(Repository.repo_name == repo_name)
68
88
69 if cache:
89 if cache:
70 repo = repo.options(FromCache("sql_cache_short",
90 repo = repo.options(FromCache("sql_cache_short",
71 "get_repo_%s" % repo_name))
91 "get_repo_%s" % repo_name))
72 return repo.scalar()
92 return repo.scalar()
73
93
74 def get_users_js(self):
94 def get_users_js(self):
75
95
76 users = self.sa.query(User).filter(User.active == True).all()
96 users = self.sa.query(User).filter(User.active == True).all()
77 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
97 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
78 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
98 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
79 u.lastname, u.username)
99 u.lastname, u.username)
80 for u in users])
100 for u in users])
81 return users_array
101 return users_array
82
102
83 def get_users_groups_js(self):
103 def get_users_groups_js(self):
84 users_groups = self.sa.query(UsersGroup)\
104 users_groups = self.sa.query(UsersGroup)\
85 .filter(UsersGroup.users_group_active == True).all()
105 .filter(UsersGroup.users_group_active == True).all()
86
106
87 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
107 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
88
108
89 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
109 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
90 (gr.users_group_id, gr.users_group_name,
110 (gr.users_group_id, gr.users_group_name,
91 len(gr.members))
111 len(gr.members))
92 for gr in users_groups])
112 for gr in users_groups])
93 return users_groups_array
113 return users_groups_array
94
114
95 def _get_defaults(self, repo_name):
115 def _get_defaults(self, repo_name):
96 """
116 """
97 Get's information about repository, and returns a dict for
117 Get's information about repository, and returns a dict for
98 usage in forms
118 usage in forms
99
119
100 :param repo_name:
120 :param repo_name:
101 """
121 """
102
122
103 repo_info = Repository.get_by_repo_name(repo_name)
123 repo_info = Repository.get_by_repo_name(repo_name)
104
124
105 if repo_info is None:
125 if repo_info is None:
106 return None
126 return None
107
127
108 defaults = repo_info.get_dict()
128 defaults = repo_info.get_dict()
109 group, repo_name = repo_info.groups_and_repo
129 group, repo_name = repo_info.groups_and_repo
110 defaults['repo_name'] = repo_name
130 defaults['repo_name'] = repo_name
111 defaults['repo_group'] = getattr(group[-1] if group else None,
131 defaults['repo_group'] = getattr(group[-1] if group else None,
112 'group_id', None)
132 'group_id', None)
113
133
114 # fill owner
134 # fill owner
115 if repo_info.user:
135 if repo_info.user:
116 defaults.update({'user': repo_info.user.username})
136 defaults.update({'user': repo_info.user.username})
117 else:
137 else:
118 replacement_user = User.query().filter(User.admin ==
138 replacement_user = User.query().filter(User.admin ==
119 True).first().username
139 True).first().username
120 defaults.update({'user': replacement_user})
140 defaults.update({'user': replacement_user})
121
141
122 # fill repository users
142 # fill repository users
123 for p in repo_info.repo_to_perm:
143 for p in repo_info.repo_to_perm:
124 defaults.update({'u_perm_%s' % p.user.username:
144 defaults.update({'u_perm_%s' % p.user.username:
125 p.permission.permission_name})
145 p.permission.permission_name})
126
146
127 # fill repository groups
147 # fill repository groups
128 for p in repo_info.users_group_to_perm:
148 for p in repo_info.users_group_to_perm:
129 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
149 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
130 p.permission.permission_name})
150 p.permission.permission_name})
131
151
132 return defaults
152 return defaults
133
153
134 def update(self, repo_name, form_data):
154 def update(self, repo_name, form_data):
135 try:
155 try:
136 cur_repo = self.get_by_repo_name(repo_name, cache=False)
156 cur_repo = self.get_by_repo_name(repo_name, cache=False)
137
157
138 # update permissions
158 # update permissions
139 for member, perm, member_type in form_data['perms_updates']:
159 for member, perm, member_type in form_data['perms_updates']:
140 if member_type == 'user':
160 if member_type == 'user':
141 _member = User.get_by_username(member)
161 # this updates existing one
142 r2p = self.sa.query(UserRepoToPerm)\
162 RepoModel().grant_user_permission(
143 .filter(UserRepoToPerm.user == _member)\
163 repo=cur_repo, user=member, perm=perm
144 .filter(UserRepoToPerm.repository == cur_repo)\
164 )
145 .one()
146
147 r2p.permission = self.sa.query(Permission)\
148 .filter(Permission.permission_name ==
149 perm).scalar()
150 self.sa.add(r2p)
151 else:
165 else:
152 g2p = self.sa.query(UsersGroupRepoToPerm)\
166 RepoModel().grant_users_group_permission(
153 .filter(UsersGroupRepoToPerm.users_group ==
167 repo=cur_repo, group_name=member, perm=perm
154 UsersGroup.get_by_group_name(member))\
168 )
155 .filter(UsersGroupRepoToPerm.repository ==
156 cur_repo).one()
157
158 g2p.permission = self.sa.query(Permission)\
159 .filter(Permission.permission_name ==
160 perm).scalar()
161 self.sa.add(g2p)
162
163 # set new permissions
169 # set new permissions
164 for member, perm, member_type in form_data['perms_new']:
170 for member, perm, member_type in form_data['perms_new']:
165 if member_type == 'user':
171 if member_type == 'user':
166 r2p = UserRepoToPerm()
172 RepoModel().grant_user_permission(
167 r2p.repository = cur_repo
173 repo=cur_repo, user=member, perm=perm
168 r2p.user = User.get_by_username(member)
174 )
169
170 r2p.permission = self.sa.query(Permission)\
171 .filter(Permission.
172 permission_name == perm)\
173 .scalar()
174 self.sa.add(r2p)
175 else:
175 else:
176 g2p = UsersGroupRepoToPerm()
176 RepoModel().grant_users_group_permission(
177 g2p.repository = cur_repo
177 repo=cur_repo, group_name=member, perm=perm
178 g2p.users_group = UsersGroup.get_by_group_name(member)
178 )
179 g2p.permission = self.sa.query(Permission)\
180 .filter(Permission.
181 permission_name == perm)\
182 .scalar()
183 self.sa.add(g2p)
184
179
185 # update current repo
180 # update current repo
186 for k, v in form_data.items():
181 for k, v in form_data.items():
187 if k == 'user':
182 if k == 'user':
188 cur_repo.user = User.get_by_username(v)
183 cur_repo.user = User.get_by_username(v)
189 elif k == 'repo_name':
184 elif k == 'repo_name':
190 pass
185 pass
191 elif k == 'repo_group':
186 elif k == 'repo_group':
192 cur_repo.group = RepoGroup.get(v)
187 cur_repo.group = RepoGroup.get(v)
193
188
194 else:
189 else:
195 setattr(cur_repo, k, v)
190 setattr(cur_repo, k, v)
196
191
197 new_name = cur_repo.get_new_name(form_data['repo_name'])
192 new_name = cur_repo.get_new_name(form_data['repo_name'])
198 cur_repo.repo_name = new_name
193 cur_repo.repo_name = new_name
199
194
200 self.sa.add(cur_repo)
195 self.sa.add(cur_repo)
201
196
202 if repo_name != new_name:
197 if repo_name != new_name:
203 # rename repository
198 # rename repository
204 self.__rename_repo(old=repo_name, new=new_name)
199 self.__rename_repo(old=repo_name, new=new_name)
205
200
206 return cur_repo
201 return cur_repo
207 except:
202 except:
208 log.error(traceback.format_exc())
203 log.error(traceback.format_exc())
209 raise
204 raise
210
205
211 def create(self, form_data, cur_user, just_db=False, fork=False):
206 def create(self, form_data, cur_user, just_db=False, fork=False):
212 from rhodecode.model.scm import ScmModel
207 from rhodecode.model.scm import ScmModel
213
208
214 try:
209 try:
215 if fork:
210 if fork:
216 fork_parent_id = form_data['fork_parent_id']
211 fork_parent_id = form_data['fork_parent_id']
217
212
218 # repo name is just a name of repository
213 # repo name is just a name of repository
219 # while repo_name_full is a full qualified name that is combined
214 # while repo_name_full is a full qualified name that is combined
220 # with name and path of group
215 # with name and path of group
221 repo_name = form_data['repo_name']
216 repo_name = form_data['repo_name']
222 repo_name_full = form_data['repo_name_full']
217 repo_name_full = form_data['repo_name_full']
223
218
224 new_repo = Repository()
219 new_repo = Repository()
225 new_repo.enable_statistics = False
220 new_repo.enable_statistics = False
226
221
227 for k, v in form_data.items():
222 for k, v in form_data.items():
228 if k == 'repo_name':
223 if k == 'repo_name':
229 v = repo_name_full
224 v = repo_name_full
230 if k == 'repo_group':
225 if k == 'repo_group':
231 k = 'group_id'
226 k = 'group_id'
232 if k == 'description':
227 if k == 'description':
233 v = v or repo_name
228 v = v or repo_name
234
229
235 setattr(new_repo, k, v)
230 setattr(new_repo, k, v)
236
231
237 if fork:
232 if fork:
238 parent_repo = Repository.get(fork_parent_id)
233 parent_repo = Repository.get(fork_parent_id)
239 new_repo.fork = parent_repo
234 new_repo.fork = parent_repo
240
235
241 new_repo.user_id = cur_user.user_id
236 new_repo.user_id = cur_user.user_id
242 self.sa.add(new_repo)
237 self.sa.add(new_repo)
243
238
244 def _create_default_perms():
239 def _create_default_perms():
245 # create default permission
240 # create default permission
246 repo_to_perm = UserRepoToPerm()
241 repo_to_perm = UserRepoToPerm()
247 default = 'repository.read'
242 default = 'repository.read'
248 for p in User.get_by_username('default').user_perms:
243 for p in User.get_by_username('default').user_perms:
249 if p.permission.permission_name.startswith('repository.'):
244 if p.permission.permission_name.startswith('repository.'):
250 default = p.permission.permission_name
245 default = p.permission.permission_name
251 break
246 break
252
247
253 default_perm = 'repository.none' if form_data['private'] else default
248 default_perm = 'repository.none' if form_data['private'] else default
254
249
255 repo_to_perm.permission_id = self.sa.query(Permission)\
250 repo_to_perm.permission_id = self.sa.query(Permission)\
256 .filter(Permission.permission_name == default_perm)\
251 .filter(Permission.permission_name == default_perm)\
257 .one().permission_id
252 .one().permission_id
258
253
259 repo_to_perm.repository = new_repo
254 repo_to_perm.repository = new_repo
260 repo_to_perm.user_id = User.get_by_username('default').user_id
255 repo_to_perm.user_id = User.get_by_username('default').user_id
261
256
262 self.sa.add(repo_to_perm)
257 self.sa.add(repo_to_perm)
263
258
264 if fork:
259 if fork:
265 if form_data.get('copy_permissions'):
260 if form_data.get('copy_permissions'):
266 repo = Repository.get(fork_parent_id)
261 repo = Repository.get(fork_parent_id)
267 user_perms = UserRepoToPerm.query()\
262 user_perms = UserRepoToPerm.query()\
268 .filter(UserRepoToPerm.repository == repo).all()
263 .filter(UserRepoToPerm.repository == repo).all()
269 group_perms = UsersGroupRepoToPerm.query()\
264 group_perms = UsersGroupRepoToPerm.query()\
270 .filter(UsersGroupRepoToPerm.repository == repo).all()
265 .filter(UsersGroupRepoToPerm.repository == repo).all()
271
266
272 for perm in user_perms:
267 for perm in user_perms:
273 UserRepoToPerm.create(perm.user, new_repo,
268 UserRepoToPerm.create(perm.user, new_repo,
274 perm.permission)
269 perm.permission)
275
270
276 for perm in group_perms:
271 for perm in group_perms:
277 UsersGroupRepoToPerm.create(perm.users_group, new_repo,
272 UsersGroupRepoToPerm.create(perm.users_group, new_repo,
278 perm.permission)
273 perm.permission)
279 else:
274 else:
280 _create_default_perms()
275 _create_default_perms()
281 else:
276 else:
282 _create_default_perms()
277 _create_default_perms()
283
278
284 if not just_db:
279 if not just_db:
285 self.__create_repo(repo_name, form_data['repo_type'],
280 self.__create_repo(repo_name, form_data['repo_type'],
286 form_data['repo_group'],
281 form_data['repo_group'],
287 form_data['clone_uri'])
282 form_data['clone_uri'])
288
283
289 # now automatically start following this repository as owner
284 # now automatically start following this repository as owner
290 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
285 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
291 cur_user.user_id)
286 cur_user.user_id)
292 log_create_repository(new_repo.get_dict(),
287 log_create_repository(new_repo.get_dict(),
293 created_by=cur_user.username)
288 created_by=cur_user.username)
294 return new_repo
289 return new_repo
295 except:
290 except:
296 log.error(traceback.format_exc())
291 log.error(traceback.format_exc())
297 raise
292 raise
298
293
299 def create_fork(self, form_data, cur_user):
294 def create_fork(self, form_data, cur_user):
300 """
295 """
301 Simple wrapper into executing celery task for fork creation
296 Simple wrapper into executing celery task for fork creation
302
297
303 :param form_data:
298 :param form_data:
304 :param cur_user:
299 :param cur_user:
305 """
300 """
306 from rhodecode.lib.celerylib import tasks, run_task
301 from rhodecode.lib.celerylib import tasks, run_task
307 run_task(tasks.create_repo_fork, form_data, cur_user)
302 run_task(tasks.create_repo_fork, form_data, cur_user)
308
303
309 def delete(self, repo):
304 def delete(self, repo):
310 try:
305 try:
311 self.sa.delete(repo)
306 self.sa.delete(repo)
312 self.__delete_repo(repo)
307 self.__delete_repo(repo)
313 except:
308 except:
314 log.error(traceback.format_exc())
309 log.error(traceback.format_exc())
315 raise
310 raise
316
311
317 def delete_perm_user(self, form_data, repo_name):
312 def grant_user_permission(self, repo, user, perm):
318 try:
313 """
319 obj = self.sa.query(UserRepoToPerm)\
314 Grant permission for user on given repository, or update existing one
320 .filter(UserRepoToPerm.repository \
315 if found
321 == self.get_by_repo_name(repo_name))\
316
322 .filter(UserRepoToPerm.user_id == form_data['user_id']).one()
317 :param repo: Instance of Repository, repository_id, or repository name
323 self.sa.delete(obj)
318 :param user: Instance of User, user_id or username
324 except:
319 :param perm: Instance of Permission, or permission_name
325 log.error(traceback.format_exc())
320 """
326 raise
321 user = self.__get_user(user)
322 repo = self.__get_repo(repo)
323 permission = self.__get_perm(perm)
324
325 # check if we have that permission already
326 obj = self.sa.query(UserRepoToPerm)\
327 .filter(UserRepoToPerm.user == user)\
328 .filter(UserRepoToPerm.repository == repo)\
329 .scalar()
330 if obj is None:
331 # create new !
332 obj = UserRepoToPerm()
333 obj.repository = repo
334 obj.user = user
335 obj.permission = permission
336 self.sa.add(obj)
337
338 def revoke_user_permission(self, repo, user):
339 """
340 Revoke permission for user on given repository
341
342 :param repo: Instance of Repository, repository_id, or repository name
343 :param user: Instance of User, user_id or username
344 """
345 user = self.__get_user(user)
346 repo = self.__get_repo(repo)
347
348 obj = self.sa.query(UserRepoToPerm)\
349 .filter(UserRepoToPerm.repository == repo)\
350 .filter(UserRepoToPerm.user == user)\
351 .one()
352 self.sa.delete(obj)
327
353
328 def delete_perm_users_group(self, form_data, repo_name):
354 def grant_users_group_permission(self, repo, group_name, perm):
329 try:
355 """
330 obj = self.sa.query(UsersGroupRepoToPerm)\
356 Grant permission for users group on given repository, or update
331 .filter(UsersGroupRepoToPerm.repository \
357 existing one if found
332 == self.get_by_repo_name(repo_name))\
358
333 .filter(UsersGroupRepoToPerm.users_group_id
359 :param repo: Instance of Repository, repository_id, or repository name
334 == form_data['users_group_id']).one()
360 :param group_name: Instance of UserGroup, users_group_id,
335 self.sa.delete(obj)
361 or users group name
336 except:
362 :param perm: Instance of Permission, or permission_name
337 log.error(traceback.format_exc())
363 """
338 raise
364 repo = self.__get_repo(repo)
365 group_name = self.__get_users_group(group_name)
366 permission = self.__get_perm(perm)
367
368 # check if we have that permission already
369 obj = self.sa.query(UsersGroupRepoToPerm)\
370 .filter(UsersGroupRepoToPerm.users_group == group_name)\
371 .filter(UsersGroupRepoToPerm.repository == repo)\
372 .scalar()
373
374 if obj is None:
375 # create new
376 obj = UsersGroupRepoToPerm()
377
378 obj.repository = repo
379 obj.users_group = group_name
380 obj.permission = permission
381 self.sa.add(obj)
382
383 def revoke_users_group_permission(self, repo, group_name):
384 """
385 Revoke permission for users group on given repository
386
387 :param repo: Instance of Repository, repository_id, or repository name
388 :param group_name: Instance of UserGroup, users_group_id,
389 or users group name
390 """
391 repo = self.__get_repo(repo)
392 group_name = self.__get_users_group(group_name)
393
394 obj = self.sa.query(UsersGroupRepoToPerm)\
395 .filter(UsersGroupRepoToPerm.repository == repo)\
396 .filter(UsersGroupRepoToPerm.users_group == group_name)\
397 .one()
398 self.sa.delete(obj)
339
399
340 def delete_stats(self, repo_name):
400 def delete_stats(self, repo_name):
341 """
401 """
342 removes stats for given repo
402 removes stats for given repo
343
403
344 :param repo_name:
404 :param repo_name:
345 """
405 """
346 try:
406 try:
347 obj = self.sa.query(Statistics)\
407 obj = self.sa.query(Statistics)\
348 .filter(Statistics.repository == \
408 .filter(Statistics.repository ==
349 self.get_by_repo_name(repo_name)).one()
409 self.get_by_repo_name(repo_name))\
410 .one()
350 self.sa.delete(obj)
411 self.sa.delete(obj)
351 except:
412 except:
352 log.error(traceback.format_exc())
413 log.error(traceback.format_exc())
353 raise
414 raise
354
415
355 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
416 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
356 """
417 """
357 makes repository on filesystem. It's group aware means it'll create
418 makes repository on filesystem. It's group aware means it'll create
358 a repository within a group, and alter the paths accordingly of
419 a repository within a group, and alter the paths accordingly of
359 group location
420 group location
360
421
361 :param repo_name:
422 :param repo_name:
362 :param alias:
423 :param alias:
363 :param parent_id:
424 :param parent_id:
364 :param clone_uri:
425 :param clone_uri:
365 """
426 """
366 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
427 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
367
428
368 if new_parent_id:
429 if new_parent_id:
369 paths = RepoGroup.get(new_parent_id)\
430 paths = RepoGroup.get(new_parent_id)\
370 .full_path.split(RepoGroup.url_sep())
431 .full_path.split(RepoGroup.url_sep())
371 new_parent_path = os.sep.join(paths)
432 new_parent_path = os.sep.join(paths)
372 else:
433 else:
373 new_parent_path = ''
434 new_parent_path = ''
374
435
375 # we need to make it str for mercurial
436 # we need to make it str for mercurial
376 repo_path = os.path.join(*map(lambda x:safe_str(x),
437 repo_path = os.path.join(*map(lambda x: safe_str(x),
377 [self.repos_path, new_parent_path, repo_name]))
438 [self.repos_path, new_parent_path, repo_name]))
378
439
379
380 # check if this path is not a repository
440 # check if this path is not a repository
381 if is_valid_repo(repo_path, self.repos_path):
441 if is_valid_repo(repo_path, self.repos_path):
382 raise Exception('This path %s is a valid repository' % repo_path)
442 raise Exception('This path %s is a valid repository' % repo_path)
383
443
384 # check if this path is a group
444 # check if this path is a group
385 if is_valid_repos_group(repo_path, self.repos_path):
445 if is_valid_repos_group(repo_path, self.repos_path):
386 raise Exception('This path %s is a valid group' % repo_path)
446 raise Exception('This path %s is a valid group' % repo_path)
387
447
388 log.info('creating repo %s in %s @ %s' % (
448 log.info('creating repo %s in %s @ %s' % (
389 repo_name, safe_unicode(repo_path), clone_uri
449 repo_name, safe_unicode(repo_path), clone_uri
390 )
450 )
391 )
451 )
392 backend = get_backend(alias)
452 backend = get_backend(alias)
393
453
394 backend(repo_path, create=True, src_url=clone_uri)
454 backend(repo_path, create=True, src_url=clone_uri)
395
455
396
397 def __rename_repo(self, old, new):
456 def __rename_repo(self, old, new):
398 """
457 """
399 renames repository on filesystem
458 renames repository on filesystem
400
459
401 :param old: old name
460 :param old: old name
402 :param new: new name
461 :param new: new name
403 """
462 """
404 log.info('renaming repo from %s to %s' % (old, new))
463 log.info('renaming repo from %s to %s' % (old, new))
405
464
406 old_path = os.path.join(self.repos_path, old)
465 old_path = os.path.join(self.repos_path, old)
407 new_path = os.path.join(self.repos_path, new)
466 new_path = os.path.join(self.repos_path, new)
408 if os.path.isdir(new_path):
467 if os.path.isdir(new_path):
409 raise Exception('Was trying to rename to already existing dir %s' \
468 raise Exception(
410 % new_path)
469 'Was trying to rename to already existing dir %s' % new_path
470 )
411 shutil.move(old_path, new_path)
471 shutil.move(old_path, new_path)
412
472
413 def __delete_repo(self, repo):
473 def __delete_repo(self, repo):
414 """
474 """
415 removes repo from filesystem, the removal is acctually made by
475 removes repo from filesystem, the removal is acctually made by
416 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
476 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
417 repository is no longer valid for rhodecode, can be undeleted later on
477 repository is no longer valid for rhodecode, can be undeleted later on
418 by reverting the renames on this repository
478 by reverting the renames on this repository
419
479
420 :param repo: repo object
480 :param repo: repo object
421 """
481 """
422 rm_path = os.path.join(self.repos_path, repo.repo_name)
482 rm_path = os.path.join(self.repos_path, repo.repo_name)
423 log.info("Removing %s" % (rm_path))
483 log.info("Removing %s" % (rm_path))
424 # disable hg/git
484 # disable hg/git
425 alias = repo.repo_type
485 alias = repo.repo_type
426 shutil.move(os.path.join(rm_path, '.%s' % alias),
486 shutil.move(os.path.join(rm_path, '.%s' % alias),
427 os.path.join(rm_path, 'rm__.%s' % alias))
487 os.path.join(rm_path, 'rm__.%s' % alias))
428 # disable repo
488 # disable repo
429 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
489 _d = 'rm__%s__%s' % (datetime.now().strftime('%Y%m%d_%H%M%S_%f'),
430 % (datetime.today()\
490 repo.repo_name)
431 .strftime('%Y%m%d_%H%M%S_%f'),
491 shutil.move(rm_path, os.path.join(self.repos_path, _d))
432 repo.repo_name)))
@@ -1,97 +1,112 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.users_group
3 rhodecode.model.users_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 repository permission model for RhodeCode
6 repository permission model for RhodeCode
7
7
8 :created_on: Oct 1, 2011
8 :created_on: Oct 1, 2011
9 :author: nvinot, marcink
9 :author: nvinot, marcink
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import logging
27 import logging
28 from rhodecode.model import BaseModel
28 from rhodecode.model import BaseModel
29 from rhodecode.model.db import UserRepoToPerm, UsersGroupRepoToPerm, Permission
29 from rhodecode.model.db import UserRepoToPerm, UsersGroupRepoToPerm, Permission,\
30 User, Repository
30
31
31 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
32
33
33
34
34 class RepositoryPermissionModel(BaseModel):
35 class RepositoryPermissionModel(BaseModel):
35
36
37 def __get_user(self, user):
38 return self._get_instance(User, user, callback=User.get_by_username)
39
40 def __get_repo(self, repository):
41 return self._get_instance(Repository, repository,
42 callback=Repository.get_by_repo_name)
43
44 def __get_perm(self, permission):
45 return self._get_instance(Permission, permission,
46 callback=Permission.get_by_key)
47
36 def get_user_permission(self, repository, user):
48 def get_user_permission(self, repository, user):
49 repository = self.__get_repo(repository)
50 user = self.__get_user(user)
51
37 return UserRepoToPerm.query() \
52 return UserRepoToPerm.query() \
38 .filter(UserRepoToPerm.user == user) \
53 .filter(UserRepoToPerm.user == user) \
39 .filter(UserRepoToPerm.repository == repository) \
54 .filter(UserRepoToPerm.repository == repository) \
40 .scalar()
55 .scalar()
41
56
42 def update_user_permission(self, repository, user, permission):
57 def update_user_permission(self, repository, user, permission):
43 permission = Permission.get_by_key(permission)
58 permission = Permission.get_by_key(permission)
44 current = self.get_user_permission(repository, user)
59 current = self.get_user_permission(repository, user)
45 if current:
60 if current:
46 if not current.permission is permission:
61 if not current.permission is permission:
47 current.permission = permission
62 current.permission = permission
48 else:
63 else:
49 p = UserRepoToPerm()
64 p = UserRepoToPerm()
50 p.user = user
65 p.user = user
51 p.repository = repository
66 p.repository = repository
52 p.permission = permission
67 p.permission = permission
53 self.sa.add(p)
68 self.sa.add(p)
54
69
55 def delete_user_permission(self, repository, user):
70 def delete_user_permission(self, repository, user):
56 current = self.get_user_permission(repository, user)
71 current = self.get_user_permission(repository, user)
57 if current:
72 if current:
58 self.sa.delete(current)
73 self.sa.delete(current)
59
74
60 def get_users_group_permission(self, repository, users_group):
75 def get_users_group_permission(self, repository, users_group):
61 return UsersGroupRepoToPerm.query() \
76 return UsersGroupRepoToPerm.query() \
62 .filter(UsersGroupRepoToPerm.users_group == users_group) \
77 .filter(UsersGroupRepoToPerm.users_group == users_group) \
63 .filter(UsersGroupRepoToPerm.repository == repository) \
78 .filter(UsersGroupRepoToPerm.repository == repository) \
64 .scalar()
79 .scalar()
65
80
66 def update_users_group_permission(self, repository, users_group,
81 def update_users_group_permission(self, repository, users_group,
67 permission):
82 permission):
68 permission = Permission.get_by_key(permission)
83 permission = Permission.get_by_key(permission)
69 current = self.get_users_group_permission(repository, users_group)
84 current = self.get_users_group_permission(repository, users_group)
70 if current:
85 if current:
71 if not current.permission is permission:
86 if not current.permission is permission:
72 current.permission = permission
87 current.permission = permission
73 else:
88 else:
74 p = UsersGroupRepoToPerm()
89 p = UsersGroupRepoToPerm()
75 p.users_group = users_group
90 p.users_group = users_group
76 p.repository = repository
91 p.repository = repository
77 p.permission = permission
92 p.permission = permission
78 self.sa.add(p)
93 self.sa.add(p)
79
94
80 def delete_users_group_permission(self, repository, users_group):
95 def delete_users_group_permission(self, repository, users_group):
81 current = self.get_users_group_permission(repository, users_group)
96 current = self.get_users_group_permission(repository, users_group)
82 if current:
97 if current:
83 self.sa.delete(current)
98 self.sa.delete(current)
84
99
85 def update_or_delete_user_permission(self, repository, user, permission):
100 def update_or_delete_user_permission(self, repository, user, permission):
86 if permission:
101 if permission:
87 self.update_user_permission(repository, user, permission)
102 self.update_user_permission(repository, user, permission)
88 else:
103 else:
89 self.delete_user_permission(repository, user)
104 self.delete_user_permission(repository, user)
90
105
91 def update_or_delete_users_group_permission(self, repository, user_group,
106 def update_or_delete_users_group_permission(self, repository, user_group,
92 permission):
107 permission):
93 if permission:
108 if permission:
94 self.update_users_group_permission(repository, user_group,
109 self.update_users_group_permission(repository, user_group,
95 permission)
110 permission)
96 else:
111 else:
97 self.delete_users_group_permission(repository, user_group)
112 self.delete_users_group_permission(repository, user_group)
@@ -1,156 +1,310 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user_group
3 rhodecode.model.user_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 users groups model for RhodeCode
6 users groups model for RhodeCode
7
7
8 :created_on: Jan 25, 2011
8 :created_on: Jan 25, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import shutil
29 import shutil
30
30
31 from pylons.i18n.translation import _
31 from rhodecode.lib import LazyProperty
32
33 from vcs.utils.lazy import LazyProperty
34
32
35 from rhodecode.model import BaseModel
33 from rhodecode.model import BaseModel
36 from rhodecode.model.db import RepoGroup, RhodeCodeUi
34 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
35 User, Permission, UsersGroupRepoGroupToPerm, UsersGroup
37
36
38 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
39
38
40
39
41 class ReposGroupModel(BaseModel):
40 class ReposGroupModel(BaseModel):
42
41
42 def __get_user(self, user):
43 return self._get_instance(User, user, callback=User.get_by_username)
44
45 def __get_users_group(self, users_group):
46 return self._get_instance(UsersGroup, users_group,
47 callback=UsersGroup.get_by_group_name)
48
49 def __get_repos_group(self, repos_group):
50 return self._get_instance(RepoGroup, repos_group,
51 callback=RepoGroup.get_by_group_name)
52
53 def __get_perm(self, permission):
54 return self._get_instance(Permission, permission,
55 callback=Permission.get_by_key)
56
43 @LazyProperty
57 @LazyProperty
44 def repos_path(self):
58 def repos_path(self):
45 """
59 """
46 Get's the repositories root path from database
60 Get's the repositories root path from database
47 """
61 """
48
62
49 q = RhodeCodeUi.get_by_key('/').one()
63 q = RhodeCodeUi.get_by_key('/').one()
50 return q.ui_value
64 return q.ui_value
51
65
66 def _create_default_perms(self, new_group):
67 # create default permission
68 repo_group_to_perm = UserRepoGroupToPerm()
69 default_perm = 'group.read'
70 for p in User.get_by_username('default').user_perms:
71 if p.permission.permission_name.startswith('group.'):
72 default_perm = p.permission.permission_name
73 break
74
75 repo_group_to_perm.permission_id = self.sa.query(Permission)\
76 .filter(Permission.permission_name == default_perm)\
77 .one().permission_id
78
79 repo_group_to_perm.group = new_group
80 repo_group_to_perm.user_id = User.get_by_username('default').user_id
81
82 self.sa.add(repo_group_to_perm)
83
52 def __create_group(self, group_name):
84 def __create_group(self, group_name):
53 """
85 """
54 makes repositories group on filesystem
86 makes repositories group on filesystem
55
87
56 :param repo_name:
88 :param repo_name:
57 :param parent_id:
89 :param parent_id:
58 """
90 """
59
91
60 create_path = os.path.join(self.repos_path, group_name)
92 create_path = os.path.join(self.repos_path, group_name)
61 log.debug('creating new group in %s' % create_path)
93 log.debug('creating new group in %s' % create_path)
62
94
63 if os.path.isdir(create_path):
95 if os.path.isdir(create_path):
64 raise Exception('That directory already exists !')
96 raise Exception('That directory already exists !')
65
97
66 os.makedirs(create_path)
98 os.makedirs(create_path)
67
99
68 def __rename_group(self, old, new):
100 def __rename_group(self, old, new):
69 """
101 """
70 Renames a group on filesystem
102 Renames a group on filesystem
71
103
72 :param group_name:
104 :param group_name:
73 """
105 """
74
106
75 if old == new:
107 if old == new:
76 log.debug('skipping group rename')
108 log.debug('skipping group rename')
77 return
109 return
78
110
79 log.debug('renaming repos group from %s to %s' % (old, new))
111 log.debug('renaming repos group from %s to %s' % (old, new))
80
112
81 old_path = os.path.join(self.repos_path, old)
113 old_path = os.path.join(self.repos_path, old)
82 new_path = os.path.join(self.repos_path, new)
114 new_path = os.path.join(self.repos_path, new)
83
115
84 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
116 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
85
117
86 if os.path.isdir(new_path):
118 if os.path.isdir(new_path):
87 raise Exception('Was trying to rename to already '
119 raise Exception('Was trying to rename to already '
88 'existing dir %s' % new_path)
120 'existing dir %s' % new_path)
89 shutil.move(old_path, new_path)
121 shutil.move(old_path, new_path)
90
122
91 def __delete_group(self, group):
123 def __delete_group(self, group):
92 """
124 """
93 Deletes a group from a filesystem
125 Deletes a group from a filesystem
94
126
95 :param group: instance of group from database
127 :param group: instance of group from database
96 """
128 """
97 paths = group.full_path.split(RepoGroup.url_sep())
129 paths = group.full_path.split(RepoGroup.url_sep())
98 paths = os.sep.join(paths)
130 paths = os.sep.join(paths)
99
131
100 rm_path = os.path.join(self.repos_path, paths)
132 rm_path = os.path.join(self.repos_path, paths)
101 if os.path.isdir(rm_path):
133 if os.path.isdir(rm_path):
102 # delete only if that path really exists
134 # delete only if that path really exists
103 os.rmdir(rm_path)
135 os.rmdir(rm_path)
104
136
105 def create(self, form_data):
137 def create(self, group_name, group_description, parent, just_db=False):
106 try:
138 try:
107 new_repos_group = RepoGroup()
139 new_repos_group = RepoGroup()
108 new_repos_group.group_description = form_data['group_description']
140 new_repos_group.group_description = group_description
109 new_repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
141 new_repos_group.parent_group = self.__get_repos_group(parent)
110 new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
142 new_repos_group.group_name = new_repos_group.get_new_name(group_name)
111
143
112 self.sa.add(new_repos_group)
144 self.sa.add(new_repos_group)
113 self.sa.flush()
145 self._create_default_perms(new_repos_group)
114 self.__create_group(new_repos_group.group_name)
146
147 if not just_db:
148 # we need to flush here, in order to check if database won't
149 # throw any exceptions, create filesystem dirs at the very end
150 self.sa.flush()
151 self.__create_group(new_repos_group.group_name)
115
152
116 return new_repos_group
153 return new_repos_group
117 except:
154 except:
118 log.error(traceback.format_exc())
155 log.error(traceback.format_exc())
119 raise
156 raise
120
157
121 def update(self, repos_group_id, form_data):
158 def update(self, repos_group_id, form_data):
122
159
123 try:
160 try:
124 repos_group = RepoGroup.get(repos_group_id)
161 repos_group = RepoGroup.get(repos_group_id)
162
163 # update permissions
164 for member, perm, member_type in form_data['perms_updates']:
165 if member_type == 'user':
166 # this updates also current one if found
167 ReposGroupModel().grant_user_permission(
168 repos_group=repos_group, user=member, perm=perm
169 )
170 else:
171 ReposGroupModel().grant_users_group_permission(
172 repos_group=repos_group, group_name=member, perm=perm
173 )
174 # set new permissions
175 for member, perm, member_type in form_data['perms_new']:
176 if member_type == 'user':
177 ReposGroupModel().grant_user_permission(
178 repos_group=repos_group, user=member, perm=perm
179 )
180 else:
181 ReposGroupModel().grant_users_group_permission(
182 repos_group=repos_group, group_name=member, perm=perm
183 )
184
125 old_path = repos_group.full_path
185 old_path = repos_group.full_path
126
186
127 # change properties
187 # change properties
128 repos_group.group_description = form_data['group_description']
188 repos_group.group_description = form_data['group_description']
129 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
189 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
130 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
190 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
131
191
132 new_path = repos_group.full_path
192 new_path = repos_group.full_path
133
193
134 self.sa.add(repos_group)
194 self.sa.add(repos_group)
135
195
136 self.__rename_group(old_path, new_path)
196 self.__rename_group(old_path, new_path)
137
197
138 # we need to get all repositories from this new group and
198 # we need to get all repositories from this new group and
139 # rename them accordingly to new group path
199 # rename them accordingly to new group path
140 for r in repos_group.repositories:
200 for r in repos_group.repositories:
141 r.repo_name = r.get_new_name(r.just_name)
201 r.repo_name = r.get_new_name(r.just_name)
142 self.sa.add(r)
202 self.sa.add(r)
143
203
144 return repos_group
204 return repos_group
145 except:
205 except:
146 log.error(traceback.format_exc())
206 log.error(traceback.format_exc())
147 raise
207 raise
148
208
149 def delete(self, users_group_id):
209 def delete(self, users_group_id):
150 try:
210 try:
151 users_group = RepoGroup.get(users_group_id)
211 users_group = RepoGroup.get(users_group_id)
152 self.sa.delete(users_group)
212 self.sa.delete(users_group)
153 self.__delete_group(users_group)
213 self.__delete_group(users_group)
154 except:
214 except:
155 log.error(traceback.format_exc())
215 log.error(traceback.format_exc())
156 raise
216 raise
217
218 def grant_user_permission(self, repos_group, user, perm):
219 """
220 Grant permission for user on given repositories group, or update
221 existing one if found
222
223 :param repos_group: Instance of ReposGroup, repositories_group_id,
224 or repositories_group name
225 :param user: Instance of User, user_id or username
226 :param perm: Instance of Permission, or permission_name
227 """
228
229 repos_group = self.__get_repos_group(repos_group)
230 user = self.__get_user(user)
231 permission = self.__get_perm(perm)
232
233 # check if we have that permission already
234 obj = self.sa.query(UserRepoGroupToPerm)\
235 .filter(UserRepoGroupToPerm.user == user)\
236 .filter(UserRepoGroupToPerm.group == repos_group)\
237 .scalar()
238 if obj is None:
239 # create new !
240 obj = UserRepoGroupToPerm()
241 obj.group = repos_group
242 obj.user = user
243 obj.permission = permission
244 self.sa.add(obj)
245
246 def revoke_user_permission(self, repos_group, user):
247 """
248 Revoke permission for user on given repositories group
249
250 :param repos_group: Instance of ReposGroup, repositories_group_id,
251 or repositories_group name
252 :param user: Instance of User, user_id or username
253 """
254
255 repos_group = self.__get_repos_group(repos_group)
256 user = self.__get_user(user)
257
258 obj = self.sa.query(UserRepoGroupToPerm)\
259 .filter(UserRepoGroupToPerm.user == user)\
260 .filter(UserRepoGroupToPerm.group == repos_group)\
261 .one()
262 self.sa.delete(obj)
263
264 def grant_users_group_permission(self, repos_group, group_name, perm):
265 """
266 Grant permission for users group on given repositories group, or update
267 existing one if found
268
269 :param repos_group: Instance of ReposGroup, repositories_group_id,
270 or repositories_group name
271 :param group_name: Instance of UserGroup, users_group_id,
272 or users group name
273 :param perm: Instance of Permission, or permission_name
274 """
275 repos_group = self.__get_repos_group(repos_group)
276 group_name = self.__get_users_group(group_name)
277 permission = self.__get_perm(perm)
278
279 # check if we have that permission already
280 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
281 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
282 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
283 .scalar()
284
285 if obj is None:
286 # create new
287 obj = UsersGroupRepoGroupToPerm()
288
289 obj.group = repos_group
290 obj.users_group = group_name
291 obj.permission = permission
292 self.sa.add(obj)
293
294 def revoke_users_group_permission(self, repos_group, group_name):
295 """
296 Revoke permission for users group on given repositories group
297
298 :param repos_group: Instance of ReposGroup, repositories_group_id,
299 or repositories_group name
300 :param group_name: Instance of UserGroup, users_group_id,
301 or users group name
302 """
303 repos_group = self.__get_repos_group(repos_group)
304 group_name = self.__get_users_group(group_name)
305
306 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
307 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
308 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
309 .one()
310 self.sa.delete(obj)
@@ -1,425 +1,456 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import time
26 import time
27 import traceback
27 import traceback
28 import logging
28 import logging
29 import cStringIO
29 import cStringIO
30
30
31 from vcs import get_backend
31 from vcs import get_backend
32 from vcs.exceptions import RepositoryError
32 from vcs.exceptions import RepositoryError
33 from vcs.utils.lazy import LazyProperty
33 from vcs.utils.lazy import LazyProperty
34 from vcs.nodes import FileNode
34 from vcs.nodes import FileNode
35
35
36 from rhodecode import BACKENDS
36 from rhodecode import BACKENDS
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import safe_str
38 from rhodecode.lib import safe_str
39 from rhodecode.lib.auth import HasRepoPermissionAny
39 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
41 action_logger, EmptyChangeset
41 action_logger, EmptyChangeset
42 from rhodecode.model import BaseModel
42 from rhodecode.model import BaseModel
43 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
43 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
44 UserFollowing, UserLog, User
44 UserFollowing, UserLog, User, RepoGroup
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class UserTemp(object):
49 class UserTemp(object):
50 def __init__(self, user_id):
50 def __init__(self, user_id):
51 self.user_id = user_id
51 self.user_id = user_id
52
52
53 def __repr__(self):
53 def __repr__(self):
54 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
54 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
55
55
56
56
57 class RepoTemp(object):
57 class RepoTemp(object):
58 def __init__(self, repo_id):
58 def __init__(self, repo_id):
59 self.repo_id = repo_id
59 self.repo_id = repo_id
60
60
61 def __repr__(self):
61 def __repr__(self):
62 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
62 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
63
63
64
64
65 class CachedRepoList(object):
65 class CachedRepoList(object):
66
66
67 def __init__(self, db_repo_list, repos_path, order_by=None):
67 def __init__(self, db_repo_list, repos_path, order_by=None):
68 self.db_repo_list = db_repo_list
68 self.db_repo_list = db_repo_list
69 self.repos_path = repos_path
69 self.repos_path = repos_path
70 self.order_by = order_by
70 self.order_by = order_by
71 self.reversed = (order_by or '').startswith('-')
71 self.reversed = (order_by or '').startswith('-')
72
72
73 def __len__(self):
73 def __len__(self):
74 return len(self.db_repo_list)
74 return len(self.db_repo_list)
75
75
76 def __repr__(self):
76 def __repr__(self):
77 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
77 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
78
78
79 def __iter__(self):
79 def __iter__(self):
80 for dbr in self.db_repo_list:
80 for dbr in self.db_repo_list:
81 scmr = dbr.scm_instance_cached
81 scmr = dbr.scm_instance_cached
82 # check permission at this level
82 # check permission at this level
83 if not HasRepoPermissionAny('repository.read', 'repository.write',
83 if not HasRepoPermissionAny(
84 'repository.admin')(dbr.repo_name,
84 'repository.read', 'repository.write', 'repository.admin'
85 'get repo check'):
85 )(dbr.repo_name, 'get repo check'):
86 continue
86 continue
87
87
88 if scmr is None:
88 if scmr is None:
89 log.error('%s this repository is present in database but it '
89 log.error(
90 'cannot be created as an scm instance',
90 '%s this repository is present in database but it '
91 dbr.repo_name)
91 'cannot be created as an scm instance' % dbr.repo_name
92 )
92 continue
93 continue
93
94
94 last_change = scmr.last_change
95 last_change = scmr.last_change
95 tip = h.get_changeset_safe(scmr, 'tip')
96 tip = h.get_changeset_safe(scmr, 'tip')
96
97
97 tmp_d = {}
98 tmp_d = {}
98 tmp_d['name'] = dbr.repo_name
99 tmp_d['name'] = dbr.repo_name
99 tmp_d['name_sort'] = tmp_d['name'].lower()
100 tmp_d['name_sort'] = tmp_d['name'].lower()
100 tmp_d['description'] = dbr.description
101 tmp_d['description'] = dbr.description
101 tmp_d['description_sort'] = tmp_d['description']
102 tmp_d['description_sort'] = tmp_d['description']
102 tmp_d['last_change'] = last_change
103 tmp_d['last_change'] = last_change
103 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
104 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
104 tmp_d['tip'] = tip.raw_id
105 tmp_d['tip'] = tip.raw_id
105 tmp_d['tip_sort'] = tip.revision
106 tmp_d['tip_sort'] = tip.revision
106 tmp_d['rev'] = tip.revision
107 tmp_d['rev'] = tip.revision
107 tmp_d['contact'] = dbr.user.full_contact
108 tmp_d['contact'] = dbr.user.full_contact
108 tmp_d['contact_sort'] = tmp_d['contact']
109 tmp_d['contact_sort'] = tmp_d['contact']
109 tmp_d['owner_sort'] = tmp_d['contact']
110 tmp_d['owner_sort'] = tmp_d['contact']
110 tmp_d['repo_archives'] = list(scmr._get_archives())
111 tmp_d['repo_archives'] = list(scmr._get_archives())
111 tmp_d['last_msg'] = tip.message
112 tmp_d['last_msg'] = tip.message
112 tmp_d['author'] = tip.author
113 tmp_d['author'] = tip.author
113 tmp_d['dbrepo'] = dbr.get_dict()
114 tmp_d['dbrepo'] = dbr.get_dict()
114 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
115 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
115 yield tmp_d
116 yield tmp_d
116
117
117
118
119 class GroupList(object):
120
121 def __init__(self, db_repo_group_list):
122 self.db_repo_group_list = db_repo_group_list
123
124 def __len__(self):
125 return len(self.db_repo_group_list)
126
127 def __repr__(self):
128 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
129
130 def __iter__(self):
131 for dbgr in self.db_repo_group_list:
132 # check permission at this level
133 if not HasReposGroupPermissionAny(
134 'group.read', 'group.write', 'group.admin'
135 )(dbgr.group_name, 'get group repo check'):
136 continue
137
138 yield dbgr
139
140
118 class ScmModel(BaseModel):
141 class ScmModel(BaseModel):
119 """
142 """
120 Generic Scm Model
143 Generic Scm Model
121 """
144 """
122
145
123 def __get_repo(self, instance):
146 def __get_repo(self, instance):
124 cls = Repository
147 cls = Repository
125 if isinstance(instance, cls):
148 if isinstance(instance, cls):
126 return instance
149 return instance
127 elif isinstance(instance, int) or str(instance).isdigit():
150 elif isinstance(instance, int) or str(instance).isdigit():
128 return cls.get(instance)
151 return cls.get(instance)
129 elif isinstance(instance, basestring):
152 elif isinstance(instance, basestring):
130 return cls.get_by_repo_name(instance)
153 return cls.get_by_repo_name(instance)
131 elif instance:
154 elif instance:
132 raise Exception('given object must be int, basestr or Instance'
155 raise Exception('given object must be int, basestr or Instance'
133 ' of %s got %s' % (type(cls), type(instance)))
156 ' of %s got %s' % (type(cls), type(instance)))
134
157
135 @LazyProperty
158 @LazyProperty
136 def repos_path(self):
159 def repos_path(self):
137 """
160 """
138 Get's the repositories root path from database
161 Get's the repositories root path from database
139 """
162 """
140
163
141 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
164 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
142
165
143 return q.ui_value
166 return q.ui_value
144
167
145 def repo_scan(self, repos_path=None):
168 def repo_scan(self, repos_path=None):
146 """
169 """
147 Listing of repositories in given path. This path should not be a
170 Listing of repositories in given path. This path should not be a
148 repository itself. Return a dictionary of repository objects
171 repository itself. Return a dictionary of repository objects
149
172
150 :param repos_path: path to directory containing repositories
173 :param repos_path: path to directory containing repositories
151 """
174 """
152
175
153 if repos_path is None:
176 if repos_path is None:
154 repos_path = self.repos_path
177 repos_path = self.repos_path
155
178
156 log.info('scanning for repositories in %s' % repos_path)
179 log.info('scanning for repositories in %s' % repos_path)
157
180
158 baseui = make_ui('db')
181 baseui = make_ui('db')
159 repos = {}
182 repos = {}
160
183
161 for name, path in get_filesystem_repos(repos_path, recursive=True):
184 for name, path in get_filesystem_repos(repos_path, recursive=True):
162
185
163 # name need to be decomposed and put back together using the /
186 # name need to be decomposed and put back together using the /
164 # since this is internal storage separator for rhodecode
187 # since this is internal storage separator for rhodecode
165 name = Repository.url_sep().join(name.split(os.sep))
188 name = Repository.url_sep().join(name.split(os.sep))
166
189
167 try:
190 try:
168 if name in repos:
191 if name in repos:
169 raise RepositoryError('Duplicate repository name %s '
192 raise RepositoryError('Duplicate repository name %s '
170 'found in %s' % (name, path))
193 'found in %s' % (name, path))
171 else:
194 else:
172
195
173 klass = get_backend(path[0])
196 klass = get_backend(path[0])
174
197
175 if path[0] == 'hg' and path[0] in BACKENDS.keys():
198 if path[0] == 'hg' and path[0] in BACKENDS.keys():
176 repos[name] = klass(safe_str(path[1]), baseui=baseui)
199 repos[name] = klass(safe_str(path[1]), baseui=baseui)
177
200
178 if path[0] == 'git' and path[0] in BACKENDS.keys():
201 if path[0] == 'git' and path[0] in BACKENDS.keys():
179 repos[name] = klass(path[1])
202 repos[name] = klass(path[1])
180 except OSError:
203 except OSError:
181 continue
204 continue
182
205
183 return repos
206 return repos
184
207
185 def get_repos(self, all_repos=None, sort_key=None):
208 def get_repos(self, all_repos=None, sort_key=None):
186 """
209 """
187 Get all repos from db and for each repo create it's
210 Get all repos from db and for each repo create it's
188 backend instance and fill that backed with information from database
211 backend instance and fill that backed with information from database
189
212
190 :param all_repos: list of repository names as strings
213 :param all_repos: list of repository names as strings
191 give specific repositories list, good for filtering
214 give specific repositories list, good for filtering
192 """
215 """
193 if all_repos is None:
216 if all_repos is None:
194 all_repos = self.sa.query(Repository)\
217 all_repos = self.sa.query(Repository)\
195 .filter(Repository.group_id == None)\
218 .filter(Repository.group_id == None)\
196 .order_by(Repository.repo_name).all()
219 .order_by(Repository.repo_name).all()
197
220
198 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
221 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
199 order_by=sort_key)
222 order_by=sort_key)
200
223
201 return repo_iter
224 return repo_iter
202
225
226 def get_repos_groups(self, all_groups=None):
227 if all_groups is None:
228 all_groups = RepoGroup.query()\
229 .filter(RepoGroup.group_parent_id == None).all()
230 group_iter = GroupList(all_groups)
231
232 return group_iter
233
203 def mark_for_invalidation(self, repo_name):
234 def mark_for_invalidation(self, repo_name):
204 """Puts cache invalidation task into db for
235 """Puts cache invalidation task into db for
205 further global cache invalidation
236 further global cache invalidation
206
237
207 :param repo_name: this repo that should invalidation take place
238 :param repo_name: this repo that should invalidation take place
208 """
239 """
209 CacheInvalidation.set_invalidate(repo_name)
240 CacheInvalidation.set_invalidate(repo_name)
210 CacheInvalidation.set_invalidate(repo_name + "_README")
241 CacheInvalidation.set_invalidate(repo_name + "_README")
211
242
212 def toggle_following_repo(self, follow_repo_id, user_id):
243 def toggle_following_repo(self, follow_repo_id, user_id):
213
244
214 f = self.sa.query(UserFollowing)\
245 f = self.sa.query(UserFollowing)\
215 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
246 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
216 .filter(UserFollowing.user_id == user_id).scalar()
247 .filter(UserFollowing.user_id == user_id).scalar()
217
248
218 if f is not None:
249 if f is not None:
219 try:
250 try:
220 self.sa.delete(f)
251 self.sa.delete(f)
221 action_logger(UserTemp(user_id),
252 action_logger(UserTemp(user_id),
222 'stopped_following_repo',
253 'stopped_following_repo',
223 RepoTemp(follow_repo_id))
254 RepoTemp(follow_repo_id))
224 return
255 return
225 except:
256 except:
226 log.error(traceback.format_exc())
257 log.error(traceback.format_exc())
227 raise
258 raise
228
259
229 try:
260 try:
230 f = UserFollowing()
261 f = UserFollowing()
231 f.user_id = user_id
262 f.user_id = user_id
232 f.follows_repo_id = follow_repo_id
263 f.follows_repo_id = follow_repo_id
233 self.sa.add(f)
264 self.sa.add(f)
234
265
235 action_logger(UserTemp(user_id),
266 action_logger(UserTemp(user_id),
236 'started_following_repo',
267 'started_following_repo',
237 RepoTemp(follow_repo_id))
268 RepoTemp(follow_repo_id))
238 except:
269 except:
239 log.error(traceback.format_exc())
270 log.error(traceback.format_exc())
240 raise
271 raise
241
272
242 def toggle_following_user(self, follow_user_id, user_id):
273 def toggle_following_user(self, follow_user_id, user_id):
243 f = self.sa.query(UserFollowing)\
274 f = self.sa.query(UserFollowing)\
244 .filter(UserFollowing.follows_user_id == follow_user_id)\
275 .filter(UserFollowing.follows_user_id == follow_user_id)\
245 .filter(UserFollowing.user_id == user_id).scalar()
276 .filter(UserFollowing.user_id == user_id).scalar()
246
277
247 if f is not None:
278 if f is not None:
248 try:
279 try:
249 self.sa.delete(f)
280 self.sa.delete(f)
250 return
281 return
251 except:
282 except:
252 log.error(traceback.format_exc())
283 log.error(traceback.format_exc())
253 raise
284 raise
254
285
255 try:
286 try:
256 f = UserFollowing()
287 f = UserFollowing()
257 f.user_id = user_id
288 f.user_id = user_id
258 f.follows_user_id = follow_user_id
289 f.follows_user_id = follow_user_id
259 self.sa.add(f)
290 self.sa.add(f)
260 except:
291 except:
261 log.error(traceback.format_exc())
292 log.error(traceback.format_exc())
262 raise
293 raise
263
294
264 def is_following_repo(self, repo_name, user_id, cache=False):
295 def is_following_repo(self, repo_name, user_id, cache=False):
265 r = self.sa.query(Repository)\
296 r = self.sa.query(Repository)\
266 .filter(Repository.repo_name == repo_name).scalar()
297 .filter(Repository.repo_name == repo_name).scalar()
267
298
268 f = self.sa.query(UserFollowing)\
299 f = self.sa.query(UserFollowing)\
269 .filter(UserFollowing.follows_repository == r)\
300 .filter(UserFollowing.follows_repository == r)\
270 .filter(UserFollowing.user_id == user_id).scalar()
301 .filter(UserFollowing.user_id == user_id).scalar()
271
302
272 return f is not None
303 return f is not None
273
304
274 def is_following_user(self, username, user_id, cache=False):
305 def is_following_user(self, username, user_id, cache=False):
275 u = User.get_by_username(username)
306 u = User.get_by_username(username)
276
307
277 f = self.sa.query(UserFollowing)\
308 f = self.sa.query(UserFollowing)\
278 .filter(UserFollowing.follows_user == u)\
309 .filter(UserFollowing.follows_user == u)\
279 .filter(UserFollowing.user_id == user_id).scalar()
310 .filter(UserFollowing.user_id == user_id).scalar()
280
311
281 return f is not None
312 return f is not None
282
313
283 def get_followers(self, repo_id):
314 def get_followers(self, repo_id):
284 if not isinstance(repo_id, int):
315 if not isinstance(repo_id, int):
285 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
316 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
286
317
287 return self.sa.query(UserFollowing)\
318 return self.sa.query(UserFollowing)\
288 .filter(UserFollowing.follows_repo_id == repo_id).count()
319 .filter(UserFollowing.follows_repo_id == repo_id).count()
289
320
290 def get_forks(self, repo_id):
321 def get_forks(self, repo_id):
291 if not isinstance(repo_id, int):
322 if not isinstance(repo_id, int):
292 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
323 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
293
324
294 return self.sa.query(Repository)\
325 return self.sa.query(Repository)\
295 .filter(Repository.fork_id == repo_id).count()
326 .filter(Repository.fork_id == repo_id).count()
296
327
297 def mark_as_fork(self, repo, fork, user):
328 def mark_as_fork(self, repo, fork, user):
298 repo = self.__get_repo(repo)
329 repo = self.__get_repo(repo)
299 fork = self.__get_repo(fork)
330 fork = self.__get_repo(fork)
300 repo.fork = fork
331 repo.fork = fork
301 self.sa.add(repo)
332 self.sa.add(repo)
302 return repo
333 return repo
303
334
304 def pull_changes(self, repo_name, username):
335 def pull_changes(self, repo_name, username):
305 dbrepo = Repository.get_by_repo_name(repo_name)
336 dbrepo = Repository.get_by_repo_name(repo_name)
306 clone_uri = dbrepo.clone_uri
337 clone_uri = dbrepo.clone_uri
307 if not clone_uri:
338 if not clone_uri:
308 raise Exception("This repository doesn't have a clone uri")
339 raise Exception("This repository doesn't have a clone uri")
309
340
310 repo = dbrepo.scm_instance
341 repo = dbrepo.scm_instance
311 try:
342 try:
312 extras = {'ip': '',
343 extras = {'ip': '',
313 'username': username,
344 'username': username,
314 'action': 'push_remote',
345 'action': 'push_remote',
315 'repository': repo_name}
346 'repository': repo_name}
316
347
317 #inject ui extra param to log this action via push logger
348 #inject ui extra param to log this action via push logger
318 for k, v in extras.items():
349 for k, v in extras.items():
319 repo._repo.ui.setconfig('rhodecode_extras', k, v)
350 repo._repo.ui.setconfig('rhodecode_extras', k, v)
320
351
321 repo.pull(clone_uri)
352 repo.pull(clone_uri)
322 self.mark_for_invalidation(repo_name)
353 self.mark_for_invalidation(repo_name)
323 except:
354 except:
324 log.error(traceback.format_exc())
355 log.error(traceback.format_exc())
325 raise
356 raise
326
357
327 def commit_change(self, repo, repo_name, cs, user, author, message,
358 def commit_change(self, repo, repo_name, cs, user, author, message,
328 content, f_path):
359 content, f_path):
329
360
330 if repo.alias == 'hg':
361 if repo.alias == 'hg':
331 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
362 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
332 elif repo.alias == 'git':
363 elif repo.alias == 'git':
333 from vcs.backends.git import GitInMemoryChangeset as IMC
364 from vcs.backends.git import GitInMemoryChangeset as IMC
334
365
335 # decoding here will force that we have proper encoded values
366 # decoding here will force that we have proper encoded values
336 # in any other case this will throw exceptions and deny commit
367 # in any other case this will throw exceptions and deny commit
337 content = safe_str(content)
368 content = safe_str(content)
338 message = safe_str(message)
369 message = safe_str(message)
339 path = safe_str(f_path)
370 path = safe_str(f_path)
340 author = safe_str(author)
371 author = safe_str(author)
341 m = IMC(repo)
372 m = IMC(repo)
342 m.change(FileNode(path, content))
373 m.change(FileNode(path, content))
343 tip = m.commit(message=message,
374 tip = m.commit(message=message,
344 author=author,
375 author=author,
345 parents=[cs], branch=cs.branch)
376 parents=[cs], branch=cs.branch)
346
377
347 new_cs = tip.short_id
378 new_cs = tip.short_id
348 action = 'push_local:%s' % new_cs
379 action = 'push_local:%s' % new_cs
349
380
350 action_logger(user, action, repo_name)
381 action_logger(user, action, repo_name)
351
382
352 self.mark_for_invalidation(repo_name)
383 self.mark_for_invalidation(repo_name)
353
384
354 def create_node(self, repo, repo_name, cs, user, author, message, content,
385 def create_node(self, repo, repo_name, cs, user, author, message, content,
355 f_path):
386 f_path):
356 if repo.alias == 'hg':
387 if repo.alias == 'hg':
357 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
388 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
358 elif repo.alias == 'git':
389 elif repo.alias == 'git':
359 from vcs.backends.git import GitInMemoryChangeset as IMC
390 from vcs.backends.git import GitInMemoryChangeset as IMC
360 # decoding here will force that we have proper encoded values
391 # decoding here will force that we have proper encoded values
361 # in any other case this will throw exceptions and deny commit
392 # in any other case this will throw exceptions and deny commit
362
393
363 if isinstance(content, (basestring,)):
394 if isinstance(content, (basestring,)):
364 content = safe_str(content)
395 content = safe_str(content)
365 elif isinstance(content, (file, cStringIO.OutputType,)):
396 elif isinstance(content, (file, cStringIO.OutputType,)):
366 content = content.read()
397 content = content.read()
367 else:
398 else:
368 raise Exception('Content is of unrecognized type %s' % (
399 raise Exception('Content is of unrecognized type %s' % (
369 type(content)
400 type(content)
370 ))
401 ))
371
402
372 message = safe_str(message)
403 message = safe_str(message)
373 path = safe_str(f_path)
404 path = safe_str(f_path)
374 author = safe_str(author)
405 author = safe_str(author)
375 m = IMC(repo)
406 m = IMC(repo)
376
407
377 if isinstance(cs, EmptyChangeset):
408 if isinstance(cs, EmptyChangeset):
378 # Emptychangeset means we we're editing empty repository
409 # Emptychangeset means we we're editing empty repository
379 parents = None
410 parents = None
380 else:
411 else:
381 parents = [cs]
412 parents = [cs]
382
413
383 m.add(FileNode(path, content=content))
414 m.add(FileNode(path, content=content))
384 tip = m.commit(message=message,
415 tip = m.commit(message=message,
385 author=author,
416 author=author,
386 parents=parents, branch=cs.branch)
417 parents=parents, branch=cs.branch)
387 new_cs = tip.short_id
418 new_cs = tip.short_id
388 action = 'push_local:%s' % new_cs
419 action = 'push_local:%s' % new_cs
389
420
390 action_logger(user, action, repo_name)
421 action_logger(user, action, repo_name)
391
422
392 self.mark_for_invalidation(repo_name)
423 self.mark_for_invalidation(repo_name)
393
424
394 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
425 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
395 """
426 """
396 recursive walk in root dir and return a set of all path in that dir
427 recursive walk in root dir and return a set of all path in that dir
397 based on repository walk function
428 based on repository walk function
398
429
399 :param repo_name: name of repository
430 :param repo_name: name of repository
400 :param revision: revision for which to list nodes
431 :param revision: revision for which to list nodes
401 :param root_path: root path to list
432 :param root_path: root path to list
402 :param flat: return as a list, if False returns a dict with decription
433 :param flat: return as a list, if False returns a dict with decription
403
434
404 """
435 """
405 _files = list()
436 _files = list()
406 _dirs = list()
437 _dirs = list()
407 try:
438 try:
408 _repo = self.__get_repo(repo_name)
439 _repo = self.__get_repo(repo_name)
409 changeset = _repo.scm_instance.get_changeset(revision)
440 changeset = _repo.scm_instance.get_changeset(revision)
410 root_path = root_path.lstrip('/')
441 root_path = root_path.lstrip('/')
411 for topnode, dirs, files in changeset.walk(root_path):
442 for topnode, dirs, files in changeset.walk(root_path):
412 for f in files:
443 for f in files:
413 _files.append(f.path if flat else {"name": f.path,
444 _files.append(f.path if flat else {"name": f.path,
414 "type": "file"})
445 "type": "file"})
415 for d in dirs:
446 for d in dirs:
416 _dirs.append(d.path if flat else {"name": d.path,
447 _dirs.append(d.path if flat else {"name": d.path,
417 "type": "dir"})
448 "type": "dir"})
418 except RepositoryError:
449 except RepositoryError:
419 log.debug(traceback.format_exc())
450 log.debug(traceback.format_exc())
420 raise
451 raise
421
452
422 return _dirs, _files
453 return _dirs, _files
423
454
424 def get_unread_journal(self):
455 def get_unread_journal(self):
425 return self.sa.query(UserLog).count()
456 return self.sa.query(UserLog).count()
@@ -1,504 +1,549 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user
3 rhodecode.model.user
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 users model for RhodeCode
6 users model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons import url
29 from pylons import url
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.lib import safe_unicode
32 from rhodecode.lib import safe_unicode
33 from rhodecode.lib.caching_query import FromCache
33 from rhodecode.lib.caching_query import FromCache
34
34
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 Notification
38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup
39 from rhodecode.lib.exceptions import DefaultUserException, \
39 from rhodecode.lib.exceptions import DefaultUserException, \
40 UserOwnsReposException
40 UserOwnsReposException
41
41
42 from sqlalchemy.exc import DatabaseError
42 from sqlalchemy.exc import DatabaseError
43 from rhodecode.lib import generate_api_key
43 from rhodecode.lib import generate_api_key
44 from sqlalchemy.orm import joinedload
44 from sqlalchemy.orm import joinedload
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 PERM_WEIGHTS = {'repository.none': 0,
49 PERM_WEIGHTS = {
50 'repository.read': 1,
50 'repository.none': 0,
51 'repository.write': 3,
51 'repository.read': 1,
52 'repository.admin': 3}
52 'repository.write': 3,
53 'repository.admin': 4,
54 'group.none': 0,
55 'group.read': 1,
56 'group.write': 3,
57 'group.admin': 4,
58 }
53
59
54
60
55 class UserModel(BaseModel):
61 class UserModel(BaseModel):
56
62
57 def __get_user(self, user):
63 def __get_user(self, user):
58 return self._get_instance(User, user)
64 return self._get_instance(User, user, callback=User.get_by_username)
65
66 def __get_perm(self, permission):
67 return self._get_instance(Permission, permission,
68 callback=Permission.get_by_key)
59
69
60 def get(self, user_id, cache=False):
70 def get(self, user_id, cache=False):
61 user = self.sa.query(User)
71 user = self.sa.query(User)
62 if cache:
72 if cache:
63 user = user.options(FromCache("sql_cache_short",
73 user = user.options(FromCache("sql_cache_short",
64 "get_user_%s" % user_id))
74 "get_user_%s" % user_id))
65 return user.get(user_id)
75 return user.get(user_id)
66
76
67 def get_by_username(self, username, cache=False, case_insensitive=False):
77 def get_by_username(self, username, cache=False, case_insensitive=False):
68
78
69 if case_insensitive:
79 if case_insensitive:
70 user = self.sa.query(User).filter(User.username.ilike(username))
80 user = self.sa.query(User).filter(User.username.ilike(username))
71 else:
81 else:
72 user = self.sa.query(User)\
82 user = self.sa.query(User)\
73 .filter(User.username == username)
83 .filter(User.username == username)
74 if cache:
84 if cache:
75 user = user.options(FromCache("sql_cache_short",
85 user = user.options(FromCache("sql_cache_short",
76 "get_user_%s" % username))
86 "get_user_%s" % username))
77 return user.scalar()
87 return user.scalar()
78
88
79 def get_by_api_key(self, api_key, cache=False):
89 def get_by_api_key(self, api_key, cache=False):
80 return User.get_by_api_key(api_key, cache)
90 return User.get_by_api_key(api_key, cache)
81
91
82 def create(self, form_data):
92 def create(self, form_data):
83 try:
93 try:
84 new_user = User()
94 new_user = User()
85 for k, v in form_data.items():
95 for k, v in form_data.items():
86 setattr(new_user, k, v)
96 setattr(new_user, k, v)
87
97
88 new_user.api_key = generate_api_key(form_data['username'])
98 new_user.api_key = generate_api_key(form_data['username'])
89 self.sa.add(new_user)
99 self.sa.add(new_user)
90 return new_user
100 return new_user
91 except:
101 except:
92 log.error(traceback.format_exc())
102 log.error(traceback.format_exc())
93 raise
103 raise
94
104
95 def create_or_update(self, username, password, email, name, lastname,
105 def create_or_update(self, username, password, email, name, lastname,
96 active=True, admin=False, ldap_dn=None):
106 active=True, admin=False, ldap_dn=None):
97 """
107 """
98 Creates a new instance if not found, or updates current one
108 Creates a new instance if not found, or updates current one
99
109
100 :param username:
110 :param username:
101 :param password:
111 :param password:
102 :param email:
112 :param email:
103 :param active:
113 :param active:
104 :param name:
114 :param name:
105 :param lastname:
115 :param lastname:
106 :param active:
116 :param active:
107 :param admin:
117 :param admin:
108 :param ldap_dn:
118 :param ldap_dn:
109 """
119 """
110
120
111 from rhodecode.lib.auth import get_crypt_password
121 from rhodecode.lib.auth import get_crypt_password
112
122
113 log.debug('Checking for %s account in RhodeCode database' % username)
123 log.debug('Checking for %s account in RhodeCode database' % username)
114 user = User.get_by_username(username, case_insensitive=True)
124 user = User.get_by_username(username, case_insensitive=True)
115 if user is None:
125 if user is None:
116 log.debug('creating new user %s' % username)
126 log.debug('creating new user %s' % username)
117 new_user = User()
127 new_user = User()
118 else:
128 else:
119 log.debug('updating user %s' % username)
129 log.debug('updating user %s' % username)
120 new_user = user
130 new_user = user
121
131
122 try:
132 try:
123 new_user.username = username
133 new_user.username = username
124 new_user.admin = admin
134 new_user.admin = admin
125 new_user.password = get_crypt_password(password)
135 new_user.password = get_crypt_password(password)
126 new_user.api_key = generate_api_key(username)
136 new_user.api_key = generate_api_key(username)
127 new_user.email = email
137 new_user.email = email
128 new_user.active = active
138 new_user.active = active
129 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
139 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
130 new_user.name = name
140 new_user.name = name
131 new_user.lastname = lastname
141 new_user.lastname = lastname
132 self.sa.add(new_user)
142 self.sa.add(new_user)
133 return new_user
143 return new_user
134 except (DatabaseError,):
144 except (DatabaseError,):
135 log.error(traceback.format_exc())
145 log.error(traceback.format_exc())
136 raise
146 raise
137
147
138 def create_for_container_auth(self, username, attrs):
148 def create_for_container_auth(self, username, attrs):
139 """
149 """
140 Creates the given user if it's not already in the database
150 Creates the given user if it's not already in the database
141
151
142 :param username:
152 :param username:
143 :param attrs:
153 :param attrs:
144 """
154 """
145 if self.get_by_username(username, case_insensitive=True) is None:
155 if self.get_by_username(username, case_insensitive=True) is None:
146
156
147 # autogenerate email for container account without one
157 # autogenerate email for container account without one
148 generate_email = lambda usr: '%s@container_auth.account' % usr
158 generate_email = lambda usr: '%s@container_auth.account' % usr
149
159
150 try:
160 try:
151 new_user = User()
161 new_user = User()
152 new_user.username = username
162 new_user.username = username
153 new_user.password = None
163 new_user.password = None
154 new_user.api_key = generate_api_key(username)
164 new_user.api_key = generate_api_key(username)
155 new_user.email = attrs['email']
165 new_user.email = attrs['email']
156 new_user.active = attrs.get('active', True)
166 new_user.active = attrs.get('active', True)
157 new_user.name = attrs['name'] or generate_email(username)
167 new_user.name = attrs['name'] or generate_email(username)
158 new_user.lastname = attrs['lastname']
168 new_user.lastname = attrs['lastname']
159
169
160 self.sa.add(new_user)
170 self.sa.add(new_user)
161 return new_user
171 return new_user
162 except (DatabaseError,):
172 except (DatabaseError,):
163 log.error(traceback.format_exc())
173 log.error(traceback.format_exc())
164 self.sa.rollback()
174 self.sa.rollback()
165 raise
175 raise
166 log.debug('User %s already exists. Skipping creation of account'
176 log.debug('User %s already exists. Skipping creation of account'
167 ' for container auth.', username)
177 ' for container auth.', username)
168 return None
178 return None
169
179
170 def create_ldap(self, username, password, user_dn, attrs):
180 def create_ldap(self, username, password, user_dn, attrs):
171 """
181 """
172 Checks if user is in database, if not creates this user marked
182 Checks if user is in database, if not creates this user marked
173 as ldap user
183 as ldap user
174
184
175 :param username:
185 :param username:
176 :param password:
186 :param password:
177 :param user_dn:
187 :param user_dn:
178 :param attrs:
188 :param attrs:
179 """
189 """
180 from rhodecode.lib.auth import get_crypt_password
190 from rhodecode.lib.auth import get_crypt_password
181 log.debug('Checking for such ldap account in RhodeCode database')
191 log.debug('Checking for such ldap account in RhodeCode database')
182 if self.get_by_username(username, case_insensitive=True) is None:
192 if self.get_by_username(username, case_insensitive=True) is None:
183
193
184 # autogenerate email for ldap account without one
194 # autogenerate email for ldap account without one
185 generate_email = lambda usr: '%s@ldap.account' % usr
195 generate_email = lambda usr: '%s@ldap.account' % usr
186
196
187 try:
197 try:
188 new_user = User()
198 new_user = User()
189 username = username.lower()
199 username = username.lower()
190 # add ldap account always lowercase
200 # add ldap account always lowercase
191 new_user.username = username
201 new_user.username = username
192 new_user.password = get_crypt_password(password)
202 new_user.password = get_crypt_password(password)
193 new_user.api_key = generate_api_key(username)
203 new_user.api_key = generate_api_key(username)
194 new_user.email = attrs['email'] or generate_email(username)
204 new_user.email = attrs['email'] or generate_email(username)
195 new_user.active = attrs.get('active', True)
205 new_user.active = attrs.get('active', True)
196 new_user.ldap_dn = safe_unicode(user_dn)
206 new_user.ldap_dn = safe_unicode(user_dn)
197 new_user.name = attrs['name']
207 new_user.name = attrs['name']
198 new_user.lastname = attrs['lastname']
208 new_user.lastname = attrs['lastname']
199
209
200 self.sa.add(new_user)
210 self.sa.add(new_user)
201 return new_user
211 return new_user
202 except (DatabaseError,):
212 except (DatabaseError,):
203 log.error(traceback.format_exc())
213 log.error(traceback.format_exc())
204 self.sa.rollback()
214 self.sa.rollback()
205 raise
215 raise
206 log.debug('this %s user exists skipping creation of ldap account',
216 log.debug('this %s user exists skipping creation of ldap account',
207 username)
217 username)
208 return None
218 return None
209
219
210 def create_registration(self, form_data):
220 def create_registration(self, form_data):
211 from rhodecode.model.notification import NotificationModel
221 from rhodecode.model.notification import NotificationModel
212
222
213 try:
223 try:
214 new_user = User()
224 new_user = User()
215 for k, v in form_data.items():
225 for k, v in form_data.items():
216 if k != 'admin':
226 if k != 'admin':
217 setattr(new_user, k, v)
227 setattr(new_user, k, v)
218
228
219 self.sa.add(new_user)
229 self.sa.add(new_user)
220 self.sa.flush()
230 self.sa.flush()
221
231
222 # notification to admins
232 # notification to admins
223 subject = _('new user registration')
233 subject = _('new user registration')
224 body = ('New user registration\n'
234 body = ('New user registration\n'
225 '---------------------\n'
235 '---------------------\n'
226 '- Username: %s\n'
236 '- Username: %s\n'
227 '- Full Name: %s\n'
237 '- Full Name: %s\n'
228 '- Email: %s\n')
238 '- Email: %s\n')
229 body = body % (new_user.username, new_user.full_name,
239 body = body % (new_user.username, new_user.full_name,
230 new_user.email)
240 new_user.email)
231 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
241 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
232 kw = {'registered_user_url': edit_url}
242 kw = {'registered_user_url': edit_url}
233 NotificationModel().create(created_by=new_user, subject=subject,
243 NotificationModel().create(created_by=new_user, subject=subject,
234 body=body, recipients=None,
244 body=body, recipients=None,
235 type_=Notification.TYPE_REGISTRATION,
245 type_=Notification.TYPE_REGISTRATION,
236 email_kwargs=kw)
246 email_kwargs=kw)
237
247
238 except:
248 except:
239 log.error(traceback.format_exc())
249 log.error(traceback.format_exc())
240 raise
250 raise
241
251
242 def update(self, user_id, form_data):
252 def update(self, user_id, form_data):
243 try:
253 try:
244 user = self.get(user_id, cache=False)
254 user = self.get(user_id, cache=False)
245 if user.username == 'default':
255 if user.username == 'default':
246 raise DefaultUserException(
256 raise DefaultUserException(
247 _("You can't Edit this user since it's"
257 _("You can't Edit this user since it's"
248 " crucial for entire application"))
258 " crucial for entire application"))
249
259
250 for k, v in form_data.items():
260 for k, v in form_data.items():
251 if k == 'new_password' and v != '':
261 if k == 'new_password' and v != '':
252 user.password = v
262 user.password = v
253 user.api_key = generate_api_key(user.username)
263 user.api_key = generate_api_key(user.username)
254 else:
264 else:
255 setattr(user, k, v)
265 setattr(user, k, v)
256
266
257 self.sa.add(user)
267 self.sa.add(user)
258 except:
268 except:
259 log.error(traceback.format_exc())
269 log.error(traceback.format_exc())
260 raise
270 raise
261
271
262 def update_my_account(self, user_id, form_data):
272 def update_my_account(self, user_id, form_data):
263 try:
273 try:
264 user = self.get(user_id, cache=False)
274 user = self.get(user_id, cache=False)
265 if user.username == 'default':
275 if user.username == 'default':
266 raise DefaultUserException(
276 raise DefaultUserException(
267 _("You can't Edit this user since it's"
277 _("You can't Edit this user since it's"
268 " crucial for entire application"))
278 " crucial for entire application"))
269 for k, v in form_data.items():
279 for k, v in form_data.items():
270 if k == 'new_password' and v != '':
280 if k == 'new_password' and v != '':
271 user.password = v
281 user.password = v
272 user.api_key = generate_api_key(user.username)
282 user.api_key = generate_api_key(user.username)
273 else:
283 else:
274 if k not in ['admin', 'active']:
284 if k not in ['admin', 'active']:
275 setattr(user, k, v)
285 setattr(user, k, v)
276
286
277 self.sa.add(user)
287 self.sa.add(user)
278 except:
288 except:
279 log.error(traceback.format_exc())
289 log.error(traceback.format_exc())
280 raise
290 raise
281
291
282 def delete(self, user):
292 def delete(self, user):
283 user = self.__get_user(user)
293 user = self.__get_user(user)
284
294
285 try:
295 try:
286 if user.username == 'default':
296 if user.username == 'default':
287 raise DefaultUserException(
297 raise DefaultUserException(
288 _("You can't remove this user since it's"
298 _("You can't remove this user since it's"
289 " crucial for entire application"))
299 " crucial for entire application"))
290 if user.repositories:
300 if user.repositories:
291 raise UserOwnsReposException(_('This user still owns %s '
301 raise UserOwnsReposException(_('This user still owns %s '
292 'repositories and cannot be '
302 'repositories and cannot be '
293 'removed. Switch owners or '
303 'removed. Switch owners or '
294 'remove those repositories') \
304 'remove those repositories') \
295 % user.repositories)
305 % user.repositories)
296 self.sa.delete(user)
306 self.sa.delete(user)
297 except:
307 except:
298 log.error(traceback.format_exc())
308 log.error(traceback.format_exc())
299 raise
309 raise
300
310
301 def reset_password_link(self, data):
311 def reset_password_link(self, data):
302 from rhodecode.lib.celerylib import tasks, run_task
312 from rhodecode.lib.celerylib import tasks, run_task
303 run_task(tasks.send_password_link, data['email'])
313 run_task(tasks.send_password_link, data['email'])
304
314
305 def reset_password(self, data):
315 def reset_password(self, data):
306 from rhodecode.lib.celerylib import tasks, run_task
316 from rhodecode.lib.celerylib import tasks, run_task
307 run_task(tasks.reset_user_password, data['email'])
317 run_task(tasks.reset_user_password, data['email'])
308
318
309 def fill_data(self, auth_user, user_id=None, api_key=None):
319 def fill_data(self, auth_user, user_id=None, api_key=None):
310 """
320 """
311 Fetches auth_user by user_id,or api_key if present.
321 Fetches auth_user by user_id,or api_key if present.
312 Fills auth_user attributes with those taken from database.
322 Fills auth_user attributes with those taken from database.
313 Additionally set's is_authenitated if lookup fails
323 Additionally set's is_authenitated if lookup fails
314 present in database
324 present in database
315
325
316 :param auth_user: instance of user to set attributes
326 :param auth_user: instance of user to set attributes
317 :param user_id: user id to fetch by
327 :param user_id: user id to fetch by
318 :param api_key: api key to fetch by
328 :param api_key: api key to fetch by
319 """
329 """
320 if user_id is None and api_key is None:
330 if user_id is None and api_key is None:
321 raise Exception('You need to pass user_id or api_key')
331 raise Exception('You need to pass user_id or api_key')
322
332
323 try:
333 try:
324 if api_key:
334 if api_key:
325 dbuser = self.get_by_api_key(api_key)
335 dbuser = self.get_by_api_key(api_key)
326 else:
336 else:
327 dbuser = self.get(user_id)
337 dbuser = self.get(user_id)
328
338
329 if dbuser is not None and dbuser.active:
339 if dbuser is not None and dbuser.active:
330 log.debug('filling %s data' % dbuser)
340 log.debug('filling %s data' % dbuser)
331 for k, v in dbuser.get_dict().items():
341 for k, v in dbuser.get_dict().items():
332 setattr(auth_user, k, v)
342 setattr(auth_user, k, v)
333 else:
343 else:
334 return False
344 return False
335
345
336 except:
346 except:
337 log.error(traceback.format_exc())
347 log.error(traceback.format_exc())
338 auth_user.is_authenticated = False
348 auth_user.is_authenticated = False
339 return False
349 return False
340
350
341 return True
351 return True
342
352
343 def fill_perms(self, user):
353 def fill_perms(self, user):
344 """
354 """
345 Fills user permission attribute with permissions taken from database
355 Fills user permission attribute with permissions taken from database
346 works for permissions given for repositories, and for permissions that
356 works for permissions given for repositories, and for permissions that
347 are granted to groups
357 are granted to groups
348
358
349 :param user: user instance to fill his perms
359 :param user: user instance to fill his perms
350 """
360 """
351
361 RK = 'repositories'
352 user.permissions['repositories'] = {}
362 GK = 'repositories_groups'
353 user.permissions['global'] = set()
363 GLOBAL = 'global'
364 user.permissions[RK] = {}
365 user.permissions[GK] = {}
366 user.permissions[GLOBAL] = set()
354
367
355 #======================================================================
368 #======================================================================
356 # fetch default permissions
369 # fetch default permissions
357 #======================================================================
370 #======================================================================
358 default_user = User.get_by_username('default', cache=True)
371 default_user = User.get_by_username('default', cache=True)
359 default_user_id = default_user.user_id
372 default_user_id = default_user.user_id
360
373
361 default_perms = Permission.get_default_perms(default_user_id)
374 default_repo_perms = Permission.get_default_perms(default_user_id)
375 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
362
376
363 if user.is_admin:
377 if user.is_admin:
364 #==================================================================
378 #==================================================================
365 # #admin have all default rights set to admin
379 # admin user have all default rights for repositories
380 # and groups set to admin
366 #==================================================================
381 #==================================================================
367 user.permissions['global'].add('hg.admin')
382 user.permissions[GLOBAL].add('hg.admin')
368
383
369 for perm in default_perms:
384 # repositories
385 for perm in default_repo_perms:
386 r_k = perm.UserRepoToPerm.repository.repo_name
370 p = 'repository.admin'
387 p = 'repository.admin'
371 user.permissions['repositories'][perm.UserRepoToPerm.
388 user.permissions[RK][r_k] = p
372 repository.repo_name] = p
389
390 # repositories groups
391 for perm in default_repo_groups_perms:
392 rg_k = perm.UserRepoGroupToPerm.group.group_name
393 p = 'group.admin'
394 user.permissions[GK][rg_k] = p
373
395
374 else:
396 else:
375 #==================================================================
397 #==================================================================
376 # set default permissions
398 # set default permissions first for repositories and groups
377 #==================================================================
399 #==================================================================
378 uid = user.user_id
400 uid = user.user_id
379
401
380 # default global
402 # default global permissions
381 default_global_perms = self.sa.query(UserToPerm)\
403 default_global_perms = self.sa.query(UserToPerm)\
382 .filter(UserToPerm.user_id == default_user_id)
404 .filter(UserToPerm.user_id == default_user_id)
383
405
384 for perm in default_global_perms:
406 for perm in default_global_perms:
385 user.permissions['global'].add(perm.permission.permission_name)
407 user.permissions[GLOBAL].add(perm.permission.permission_name)
386
408
387 # default for repositories
409 # default for repositories
388 for perm in default_perms:
410 for perm in default_repo_perms:
389 if perm.Repository.private and not (perm.Repository.user_id ==
411 r_k = perm.UserRepoToPerm.repository.repo_name
390 uid):
412 if perm.Repository.private and not (perm.Repository.user_id == uid):
391 # disable defaults for private repos,
413 # disable defaults for private repos,
392 p = 'repository.none'
414 p = 'repository.none'
393 elif perm.Repository.user_id == uid:
415 elif perm.Repository.user_id == uid:
394 # set admin if owner
416 # set admin if owner
395 p = 'repository.admin'
417 p = 'repository.admin'
396 else:
418 else:
397 p = perm.Permission.permission_name
419 p = perm.Permission.permission_name
398
420
399 user.permissions['repositories'][perm.UserRepoToPerm.
421 user.permissions[RK][r_k] = p
400 repository.repo_name] = p
422
423 # default for repositories groups
424 for perm in default_repo_groups_perms:
425 rg_k = perm.UserRepoGroupToPerm.group.group_name
426 p = perm.Permission.permission_name
427 user.permissions[GK][rg_k] = p
401
428
402 #==================================================================
429 #==================================================================
403 # overwrite default with user permissions if any
430 # overwrite default with user permissions if any
404 #==================================================================
431 #==================================================================
405
432
406 # user global
433 # user global
407 user_perms = self.sa.query(UserToPerm)\
434 user_perms = self.sa.query(UserToPerm)\
408 .options(joinedload(UserToPerm.permission))\
435 .options(joinedload(UserToPerm.permission))\
409 .filter(UserToPerm.user_id == uid).all()
436 .filter(UserToPerm.user_id == uid).all()
410
437
411 for perm in user_perms:
438 for perm in user_perms:
412 user.permissions['global'].add(perm.permission.permission_name)
439 user.permissions[GLOBAL].add(perm.permission.permission_name)
413
440
414 # user repositories
441 # user repositories
415 user_repo_perms = self.sa.query(UserRepoToPerm, Permission,
442 user_repo_perms = \
416 Repository)\
443 self.sa.query(UserRepoToPerm, Permission, Repository)\
417 .join((Repository, UserRepoToPerm.repository_id ==
444 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
418 Repository.repo_id))\
445 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
419 .join((Permission, UserRepoToPerm.permission_id ==
446 .filter(UserRepoToPerm.user_id == uid)\
420 Permission.permission_id))\
447 .all()
421 .filter(UserRepoToPerm.user_id == uid).all()
422
448
423 for perm in user_repo_perms:
449 for perm in user_repo_perms:
424 # set admin if owner
450 # set admin if owner
451 r_k = perm.UserRepoToPerm.repository.repo_name
425 if perm.Repository.user_id == uid:
452 if perm.Repository.user_id == uid:
426 p = 'repository.admin'
453 p = 'repository.admin'
427 else:
454 else:
428 p = perm.Permission.permission_name
455 p = perm.Permission.permission_name
429 user.permissions['repositories'][perm.UserRepoToPerm.
456 user.permissions[RK][r_k] = p
430 repository.repo_name] = p
431
457
432 #==================================================================
458 #==================================================================
433 # check if user is part of groups for this repository and fill in
459 # check if user is part of groups for this repository and fill in
434 # (or replace with higher) permissions
460 # (or replace with higher) permissions
435 #==================================================================
461 #==================================================================
436
462
437 # users group global
463 # users group global
438 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
464 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
439 .options(joinedload(UsersGroupToPerm.permission))\
465 .options(joinedload(UsersGroupToPerm.permission))\
440 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
466 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
441 UsersGroupMember.users_group_id))\
467 UsersGroupMember.users_group_id))\
442 .filter(UsersGroupMember.user_id == uid).all()
468 .filter(UsersGroupMember.user_id == uid).all()
443
469
444 for perm in user_perms_from_users_groups:
470 for perm in user_perms_from_users_groups:
445 user.permissions['global'].add(perm.permission.permission_name)
471 user.permissions[GLOBAL].add(perm.permission.permission_name)
446
472
447 # users group repositories
473 # users group repositories
448 user_repo_perms_from_users_groups = self.sa.query(
474 user_repo_perms_from_users_groups = \
449 UsersGroupRepoToPerm,
475 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
450 Permission, Repository,)\
476 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
451 .join((Repository, UsersGroupRepoToPerm.repository_id ==
477 .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
452 Repository.repo_id))\
478 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
453 .join((Permission, UsersGroupRepoToPerm.permission_id ==
479 .filter(UsersGroupMember.user_id == uid)\
454 Permission.permission_id))\
480 .all()
455 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
456 UsersGroupMember.users_group_id))\
457 .filter(UsersGroupMember.user_id == uid).all()
458
481
459 for perm in user_repo_perms_from_users_groups:
482 for perm in user_repo_perms_from_users_groups:
483 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
460 p = perm.Permission.permission_name
484 p = perm.Permission.permission_name
461 cur_perm = user.permissions['repositories'][perm.
485 cur_perm = user.permissions[RK][r_k]
462 UsersGroupRepoToPerm.
463 repository.repo_name]
464 # overwrite permission only if it's greater than permission
486 # overwrite permission only if it's greater than permission
465 # given from other sources
487 # given from other sources
466 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
488 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
467 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
489 user.permissions[RK][r_k] = p
468 repository.repo_name] = p
490
491 #==================================================================
492 # get access for this user for repos group and override defaults
493 #==================================================================
494
495 # user repositories groups
496 user_repo_groups_perms = \
497 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
498 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
499 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
500 .filter(UserRepoToPerm.user_id == uid)\
501 .all()
502
503 for perm in user_repo_groups_perms:
504 rg_k = perm.UserRepoGroupToPerm.group.group_name
505 p = perm.Permission.permission_name
506 cur_perm = user.permissions[GK][rg_k]
507 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
508 user.permissions[GK][rg_k] = p
469
509
470 return user
510 return user
471
511
472 def has_perm(self, user, perm):
512 def has_perm(self, user, perm):
473 if not isinstance(perm, Permission):
513 if not isinstance(perm, Permission):
474 raise Exception('perm needs to be an instance of Permission class '
514 raise Exception('perm needs to be an instance of Permission class '
475 'got %s instead' % type(perm))
515 'got %s instead' % type(perm))
476
516
477 user = self.__get_user(user)
517 user = self.__get_user(user)
478
518
479 return UserToPerm.query().filter(UserToPerm.user == user)\
519 return UserToPerm.query().filter(UserToPerm.user == user)\
480 .filter(UserToPerm.permission == perm).scalar() is not None
520 .filter(UserToPerm.permission == perm).scalar() is not None
481
521
482 def grant_perm(self, user, perm):
522 def grant_perm(self, user, perm):
483 if not isinstance(perm, Permission):
523 """
484 raise Exception('perm needs to be an instance of Permission class '
524 Grant user global permissions
485 'got %s instead' % type(perm))
486
525
526 :param user:
527 :param perm:
528 """
487 user = self.__get_user(user)
529 user = self.__get_user(user)
488
530 perm = self.__get_perm(perm)
489 new = UserToPerm()
531 new = UserToPerm()
490 new.user = user
532 new.user = user
491 new.permission = perm
533 new.permission = perm
492 self.sa.add(new)
534 self.sa.add(new)
493
535
494 def revoke_perm(self, user, perm):
536 def revoke_perm(self, user, perm):
495 if not isinstance(perm, Permission):
537 """
496 raise Exception('perm needs to be an instance of Permission class '
538 Revoke users global permissions
497 'got %s instead' % type(perm))
498
539
540 :param user:
541 :param perm:
542 """
499 user = self.__get_user(user)
543 user = self.__get_user(user)
544 perm = self.__get_perm(perm)
500
545
501 obj = UserToPerm.query().filter(UserToPerm.user == user)\
546 obj = UserToPerm.query().filter(UserToPerm.user == user)\
502 .filter(UserToPerm.permission == perm).scalar()
547 .filter(UserToPerm.permission == perm).scalar()
503 if obj:
548 if obj:
504 self.sa.delete(obj)
549 self.sa.delete(obj)
@@ -1,151 +1,160 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.users_group
3 rhodecode.model.users_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 users group model for RhodeCode
6 users group model for RhodeCode
7
7
8 :created_on: Oct 1, 2011
8 :created_on: Oct 1, 2011
9 :author: nvinot
9 :author: nvinot
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from rhodecode.model import BaseModel
30 from rhodecode.model import BaseModel
31 from rhodecode.model.db import UsersGroupMember, UsersGroup,\
31 from rhodecode.model.db import UsersGroupMember, UsersGroup,\
32 UsersGroupRepoToPerm, Permission, UsersGroupToPerm
32 UsersGroupRepoToPerm, Permission, UsersGroupToPerm
33 from rhodecode.lib.exceptions import UsersGroupsAssignedException
33 from rhodecode.lib.exceptions import UsersGroupsAssignedException
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class UsersGroupModel(BaseModel):
38 class UsersGroupModel(BaseModel):
39
39
40 def __get_users_group(self, users_group):
40 def __get_users_group(self, users_group):
41 return self._get_instance(UsersGroup, users_group)
41 return self._get_instance(UsersGroup, users_group,
42 callback=UsersGroup.get_by_group_name)
43
44 def __get_perm(self, permission):
45 return self._get_instance(Permission, permission,
46 callback=Permission.get_by_key)
42
47
43 def get(self, users_group_id, cache=False):
48 def get(self, users_group_id, cache=False):
44 return UsersGroup.get(users_group_id)
49 return UsersGroup.get(users_group_id)
45
50
46 def get_by_name(self, name, cache=False, case_insensitive=False):
51 def get_by_name(self, name, cache=False, case_insensitive=False):
47 return UsersGroup.get_by_group_name(name, cache, case_insensitive)
52 return UsersGroup.get_by_group_name(name, cache, case_insensitive)
48
53
49 def create(self, name, active=True):
54 def create(self, name, active=True):
50 try:
55 try:
51 new = UsersGroup()
56 new = UsersGroup()
52 new.users_group_name = name
57 new.users_group_name = name
53 new.users_group_active = active
58 new.users_group_active = active
54 self.sa.add(new)
59 self.sa.add(new)
55 return new
60 return new
56 except:
61 except:
57 log.error(traceback.format_exc())
62 log.error(traceback.format_exc())
58 raise
63 raise
59
64
60 def update(self, users_group, form_data):
65 def update(self, users_group, form_data):
61
66
62 try:
67 try:
63 users_group = self.__get_users_group(users_group)
68 users_group = self.__get_users_group(users_group)
64
69
65 for k, v in form_data.items():
70 for k, v in form_data.items():
66 if k == 'users_group_members':
71 if k == 'users_group_members':
67 users_group.members = []
72 users_group.members = []
68 self.sa.flush()
73 self.sa.flush()
69 members_list = []
74 members_list = []
70 if v:
75 if v:
71 v = [v] if isinstance(v, basestring) else v
76 v = [v] if isinstance(v, basestring) else v
72 for u_id in set(v):
77 for u_id in set(v):
73 member = UsersGroupMember(users_group.users_group_id, u_id)
78 member = UsersGroupMember(users_group.users_group_id, u_id)
74 members_list.append(member)
79 members_list.append(member)
75 setattr(users_group, 'members', members_list)
80 setattr(users_group, 'members', members_list)
76 setattr(users_group, k, v)
81 setattr(users_group, k, v)
77
82
78 self.sa.add(users_group)
83 self.sa.add(users_group)
79 except:
84 except:
80 log.error(traceback.format_exc())
85 log.error(traceback.format_exc())
81 raise
86 raise
82
87
83 def delete(self, users_group):
88 def delete(self, users_group, force=False):
89 """
90 Deletes repos group, unless force flag is used
91 raises exception if there are members in that group, else deletes
92 group and users
93
94 :param users_group:
95 :param force:
96 """
84 try:
97 try:
85 users_group = self.__get_users_group(users_group)
98 users_group = self.__get_users_group(users_group)
86
99
87 # check if this group is not assigned to repo
100 # check if this group is not assigned to repo
88 assigned_groups = UsersGroupRepoToPerm.query()\
101 assigned_groups = UsersGroupRepoToPerm.query()\
89 .filter(UsersGroupRepoToPerm.users_group == users_group).all()
102 .filter(UsersGroupRepoToPerm.users_group == users_group).all()
90
103
91 if assigned_groups:
104 if assigned_groups and force is False:
92 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
105 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
93 assigned_groups)
106 assigned_groups)
94
107
95 self.sa.delete(users_group)
108 self.sa.delete(users_group)
96 except:
109 except:
97 log.error(traceback.format_exc())
110 log.error(traceback.format_exc())
98 raise
111 raise
99
112
100 def add_user_to_group(self, users_group, user):
113 def add_user_to_group(self, users_group, user):
101 for m in users_group.members:
114 for m in users_group.members:
102 u = m.user
115 u = m.user
103 if u.user_id == user.user_id:
116 if u.user_id == user.user_id:
104 return m
117 return m
105
118
106 try:
119 try:
107 users_group_member = UsersGroupMember()
120 users_group_member = UsersGroupMember()
108 users_group_member.user = user
121 users_group_member.user = user
109 users_group_member.users_group = users_group
122 users_group_member.users_group = users_group
110
123
111 users_group.members.append(users_group_member)
124 users_group.members.append(users_group_member)
112 user.group_member.append(users_group_member)
125 user.group_member.append(users_group_member)
113
126
114 self.sa.add(users_group_member)
127 self.sa.add(users_group_member)
115 return users_group_member
128 return users_group_member
116 except:
129 except:
117 log.error(traceback.format_exc())
130 log.error(traceback.format_exc())
118 raise
131 raise
119
132
120 def has_perm(self, users_group, perm):
133 def has_perm(self, users_group, perm):
121 if not isinstance(perm, Permission):
122 raise Exception('perm needs to be an instance of Permission class')
123
124 users_group = self.__get_users_group(users_group)
134 users_group = self.__get_users_group(users_group)
135 perm = self.__get_perm(perm)
125
136
126 return UsersGroupToPerm.query()\
137 return UsersGroupToPerm.query()\
127 .filter(UsersGroupToPerm.users_group == users_group)\
138 .filter(UsersGroupToPerm.users_group == users_group)\
128 .filter(UsersGroupToPerm.permission == perm).scalar() is not None
139 .filter(UsersGroupToPerm.permission == perm).scalar() is not None
129
140
130 def grant_perm(self, users_group, perm):
141 def grant_perm(self, users_group, perm):
131 if not isinstance(perm, Permission):
142 if not isinstance(perm, Permission):
132 raise Exception('perm needs to be an instance of Permission class')
143 raise Exception('perm needs to be an instance of Permission class')
133
144
134 users_group = self.__get_users_group(users_group)
145 users_group = self.__get_users_group(users_group)
135
146
136 new = UsersGroupToPerm()
147 new = UsersGroupToPerm()
137 new.users_group = users_group
148 new.users_group = users_group
138 new.permission = perm
149 new.permission = perm
139 self.sa.add(new)
150 self.sa.add(new)
140
151
141 def revoke_perm(self, users_group, perm):
152 def revoke_perm(self, users_group, perm):
142 if not isinstance(perm, Permission):
143 raise Exception('perm needs to be an instance of Permission class')
144
145 users_group = self.__get_users_group(users_group)
153 users_group = self.__get_users_group(users_group)
154 perm = self.__get_perm(perm)
146
155
147 obj = UsersGroupToPerm.query()\
156 obj = UsersGroupToPerm.query()\
148 .filter(UsersGroupToPerm.users_group == users_group)\
157 .filter(UsersGroupToPerm.users_group == users_group)\
149 .filter(UsersGroupToPerm.permission == perm).scalar()
158 .filter(UsersGroupToPerm.permission == perm).scalar()
150 if obj:
159 if obj:
151 self.sa.delete(obj)
160 self.sa.delete(obj)
@@ -1,64 +1,73 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
5 ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 &raquo;
11 &raquo;
12 ${_('edit repos group')} "${c.repos_group.name}"
12 ${_('edit repos group')} "${c.repos_group.name}"
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('admin')}
16 ${self.menu('admin')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 <!-- end box / title -->
25 <!-- end box / title -->
26 ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
26 ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="group_name">${_('Group name')}:</label>
32 <label for="group_name">${_('Group name')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('group_name',class_='medium')}
35 ${h.text('group_name',class_='medium')}
36 </div>
36 </div>
37 </div>
37 </div>
38
38
39 <div class="field">
39 <div class="field">
40 <div class="label label-textarea">
40 <div class="label label-textarea">
41 <label for="group_description">${_('Description')}:</label>
41 <label for="group_description">${_('Description')}:</label>
42 </div>
42 </div>
43 <div class="textarea text-area editor">
43 <div class="textarea text-area editor">
44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 </div>
45 </div>
46 </div>
46 </div>
47
47
48 <div class="field">
48 <div class="field">
49 <div class="label">
49 <div class="label">
50 <label for="group_parent_id">${_('Group parent')}:</label>
50 <label for="group_parent_id">${_('Group parent')}:</label>
51 </div>
51 </div>
52 <div class="input">
52 <div class="input">
53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
54 </div>
54 </div>
55 </div>
55 </div>
56 <div class="field">
57 <div class="label">
58 <label for="input">${_('Permissions')}:</label>
59 </div>
60 <div class="input">
61 <%include file="repos_group_edit_perms.html"/>
62 </div>
56
63
64 </div>
57 <div class="buttons">
65 <div class="buttons">
58 ${h.submit('save',_('save'),class_="ui-button")}
66 ${h.submit('save',_('Save'),class_="ui-button")}
67 ${h.reset('reset',_('Reset'),class_="ui-button")}
59 </div>
68 </div>
60 </div>
69 </div>
61 </div>
70 </div>
62 ${h.end_form()}
71 ${h.end_form()}
63 </div>
72 </div>
64 </%def>
73 </%def>
@@ -1,195 +1,197 b''
1 <%page args="parent" />
1 <%page args="parent" />
2 <div class="box">
2 <div class="box">
3 <!-- box / title -->
3 <!-- box / title -->
4 <div class="title">
4 <div class="title">
5 <h5>
5 <h5>
6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
7 </h5>
7 </h5>
8 %if c.rhodecode_user.username != 'default':
8 %if c.rhodecode_user.username != 'default':
9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 <ul class="links">
10 <ul class="links">
11 <li>
11 <li>
12 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
12 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
13 </li>
13 </li>
14 </ul>
14 </ul>
15 %endif
15 %endif
16 %endif
16 %endif
17 </div>
17 </div>
18 <!-- end box / title -->
18 <!-- end box / title -->
19 <div class="table">
19 <div class="table">
20 % if c.groups:
20 % if c.groups:
21 <div id='groups_list_wrap' class="yui-skin-sam">
21 <div id='groups_list_wrap' class="yui-skin-sam">
22 <table id="groups_list">
22 <table id="groups_list">
23 <thead>
23 <thead>
24 <tr>
24 <tr>
25 <th class="left"><a href="#">${_('Group name')}</a></th>
25 <th class="left"><a href="#">${_('Group name')}</a></th>
26 <th class="left"><a href="#">${_('Description')}</a></th>
26 <th class="left"><a href="#">${_('Description')}</a></th>
27 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
27 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
28 </tr>
28 </tr>
29 </thead>
29 </thead>
30
30
31 ## REPO GROUPS
31 ## REPO GROUPS
32 % for gr in c.groups:
32 % for gr in c.groups:
33 <tr>
33 <tr>
34 <td>
34 <td>
35 <div style="white-space: nowrap">
35 <div style="white-space: nowrap">
36 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
36 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
37 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
37 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
38 </div>
38 </div>
39 </td>
39 </td>
40 <td>${gr.group_description}</td>
40 <td>${gr.group_description}</td>
41 ##<td><b>${gr.repositories.count()}</b></td>
41 ## this is commented out since for multi nested repos can be HEAVY!
42 ## in number of executed queries during traversing uncomment at will
43 ##<td><b>${gr.repositories_recursive_count}</b></td>
42 </tr>
44 </tr>
43 % endfor
45 % endfor
44
46
45 </table>
47 </table>
46 </div>
48 </div>
47 <div style="height: 20px"></div>
49 <div style="height: 20px"></div>
48 % endif
50 % endif
49 <div id="welcome" style="display:none;text-align:center">
51 <div id="welcome" style="display:none;text-align:center">
50 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
52 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
51 </div>
53 </div>
52 <div id='repos_list_wrap' class="yui-skin-sam">
54 <div id='repos_list_wrap' class="yui-skin-sam">
53 <%cnt=0%>
55 <%cnt=0%>
54 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
56 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
55
57
56 <table id="repos_list">
58 <table id="repos_list">
57 <thead>
59 <thead>
58 <tr>
60 <tr>
59 <th class="left"></th>
61 <th class="left"></th>
60 <th class="left">${_('Name')}</th>
62 <th class="left">${_('Name')}</th>
61 <th class="left">${_('Description')}</th>
63 <th class="left">${_('Description')}</th>
62 <th class="left">${_('Last change')}</th>
64 <th class="left">${_('Last change')}</th>
63 <th class="left">${_('Tip')}</th>
65 <th class="left">${_('Tip')}</th>
64 <th class="left">${_('Owner')}</th>
66 <th class="left">${_('Owner')}</th>
65 <th class="left">${_('RSS')}</th>
67 <th class="left">${_('RSS')}</th>
66 <th class="left">${_('Atom')}</th>
68 <th class="left">${_('Atom')}</th>
67 </tr>
69 </tr>
68 </thead>
70 </thead>
69 <tbody>
71 <tbody>
70 %for cnt,repo in enumerate(c.repos_list,1):
72 %for cnt,repo in enumerate(c.repos_list,1):
71 <tr class="parity${cnt%2}">
73 <tr class="parity${cnt%2}">
72 ##QUICK MENU
74 ##QUICK MENU
73 <td class="quick_repo_menu">
75 <td class="quick_repo_menu">
74 ${dt.quick_menu(repo['name'])}
76 ${dt.quick_menu(repo['name'])}
75 </td>
77 </td>
76 ##REPO NAME AND ICONS
78 ##REPO NAME AND ICONS
77 <td class="reponame">
79 <td class="reponame">
78 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
80 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
79 </td>
81 </td>
80 ##DESCRIPTION
82 ##DESCRIPTION
81 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
83 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
82 ${h.truncate(repo['description'],60)}</span>
84 ${h.truncate(repo['description'],60)}</span>
83 </td>
85 </td>
84 ##LAST CHANGE DATE
86 ##LAST CHANGE DATE
85 <td>
87 <td>
86 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
88 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
87 </td>
89 </td>
88 ##LAST REVISION
90 ##LAST REVISION
89 <td>
91 <td>
90 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
92 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
91 </td>
93 </td>
92 ##
94 ##
93 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
95 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
94 <td>
96 <td>
95 %if c.rhodecode_user.username != 'default':
97 %if c.rhodecode_user.username != 'default':
96 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
98 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
97 %else:
99 %else:
98 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
100 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
99 %endif:
101 %endif:
100 </td>
102 </td>
101 <td>
103 <td>
102 %if c.rhodecode_user.username != 'default':
104 %if c.rhodecode_user.username != 'default':
103 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
105 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
104 %else:
106 %else:
105 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
107 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
106 %endif:
108 %endif:
107 </td>
109 </td>
108 </tr>
110 </tr>
109 %endfor
111 %endfor
110 </tbody>
112 </tbody>
111 </table>
113 </table>
112 </div>
114 </div>
113 </div>
115 </div>
114 </div>
116 </div>
115 <script>
117 <script>
116 YUD.get('repo_count').innerHTML = ${cnt};
118 YUD.get('repo_count').innerHTML = ${cnt};
117 var func = function(node){
119 var func = function(node){
118 return node.parentNode.parentNode.parentNode.parentNode;
120 return node.parentNode.parentNode.parentNode.parentNode;
119 }
121 }
120
122
121
123
122 // groups table sorting
124 // groups table sorting
123 var myColumnDefs = [
125 var myColumnDefs = [
124 {key:"name",label:"${_('Group Name')}",sortable:true,
126 {key:"name",label:"${_('Group Name')}",sortable:true,
125 sortOptions: { sortFunction: groupNameSort }},
127 sortOptions: { sortFunction: groupNameSort }},
126 {key:"desc",label:"${_('Description')}",sortable:true},
128 {key:"desc",label:"${_('Description')}",sortable:true},
127 ];
129 ];
128
130
129 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
131 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
130
132
131 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
133 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
132 myDataSource.responseSchema = {
134 myDataSource.responseSchema = {
133 fields: [
135 fields: [
134 {key:"name"},
136 {key:"name"},
135 {key:"desc"},
137 {key:"desc"},
136 ]
138 ]
137 };
139 };
138
140
139 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
141 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
140 {
142 {
141 sortedBy:{key:"name",dir:"asc"},
143 sortedBy:{key:"name",dir:"asc"},
142 MSG_SORTASC:"${_('Click to sort ascending')}",
144 MSG_SORTASC:"${_('Click to sort ascending')}",
143 MSG_SORTDESC:"${_('Click to sort descending')}"
145 MSG_SORTDESC:"${_('Click to sort descending')}"
144 }
146 }
145 );
147 );
146
148
147 // main table sorting
149 // main table sorting
148 var myColumnDefs = [
150 var myColumnDefs = [
149 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
151 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
150 {key:"name",label:"${_('Name')}",sortable:true,
152 {key:"name",label:"${_('Name')}",sortable:true,
151 sortOptions: { sortFunction: nameSort }},
153 sortOptions: { sortFunction: nameSort }},
152 {key:"desc",label:"${_('Description')}",sortable:true},
154 {key:"desc",label:"${_('Description')}",sortable:true},
153 {key:"last_change",label:"${_('Last Change')}",sortable:true,
155 {key:"last_change",label:"${_('Last Change')}",sortable:true,
154 sortOptions: { sortFunction: ageSort }},
156 sortOptions: { sortFunction: ageSort }},
155 {key:"tip",label:"${_('Tip')}",sortable:true,
157 {key:"tip",label:"${_('Tip')}",sortable:true,
156 sortOptions: { sortFunction: revisionSort }},
158 sortOptions: { sortFunction: revisionSort }},
157 {key:"owner",label:"${_('Owner')}",sortable:true},
159 {key:"owner",label:"${_('Owner')}",sortable:true},
158 {key:"rss",label:"",sortable:false},
160 {key:"rss",label:"",sortable:false},
159 {key:"atom",label:"",sortable:false},
161 {key:"atom",label:"",sortable:false},
160 ];
162 ];
161
163
162 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
164 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
163
165
164 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
166 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
165
167
166 myDataSource.responseSchema = {
168 myDataSource.responseSchema = {
167 fields: [
169 fields: [
168 {key:"menu"},
170 {key:"menu"},
169 {key:"name"},
171 {key:"name"},
170 {key:"desc"},
172 {key:"desc"},
171 {key:"last_change"},
173 {key:"last_change"},
172 {key:"tip"},
174 {key:"tip"},
173 {key:"owner"},
175 {key:"owner"},
174 {key:"rss"},
176 {key:"rss"},
175 {key:"atom"},
177 {key:"atom"},
176 ]
178 ]
177 };
179 };
178
180
179 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
181 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
180 {
182 {
181 sortedBy:{key:"name",dir:"asc"},
183 sortedBy:{key:"name",dir:"asc"},
182 MSG_SORTASC:"${_('Click to sort ascending')}",
184 MSG_SORTASC:"${_('Click to sort ascending')}",
183 MSG_SORTDESC:"${_('Click to sort descending')}",
185 MSG_SORTDESC:"${_('Click to sort descending')}",
184 MSG_EMPTY:"${_('No records found.')}",
186 MSG_EMPTY:"${_('No records found.')}",
185 MSG_ERROR:"${_('Data error.')}",
187 MSG_ERROR:"${_('Data error.')}",
186 MSG_LOADING:"${_('Loading...')}",
188 MSG_LOADING:"${_('Loading...')}",
187 }
189 }
188 );
190 );
189 myDataTable.subscribe('postRenderEvent',function(oArgs) {
191 myDataTable.subscribe('postRenderEvent',function(oArgs) {
190 tooltip_activate();
192 tooltip_activate();
191 quick_repo_menu();
193 quick_repo_menu();
192 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
194 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
193 });
195 });
194
196
195 </script>
197 </script>
@@ -1,404 +1,555 b''
1 import os
1 import os
2 import unittest
2 import unittest
3 from rhodecode.tests import *
3 from rhodecode.tests import *
4
4
5 from rhodecode.model.repos_group import ReposGroupModel
5 from rhodecode.model.repos_group import ReposGroupModel
6 from rhodecode.model.repo import RepoModel
6 from rhodecode.model.repo import RepoModel
7 from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
7 from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
8 UsersGroup, UsersGroupMember, Permission
8 UsersGroup, UsersGroupMember, Permission
9 from sqlalchemy.exc import IntegrityError
9 from sqlalchemy.exc import IntegrityError
10 from rhodecode.model.user import UserModel
10 from rhodecode.model.user import UserModel
11
11
12 from rhodecode.model.meta import Session
12 from rhodecode.model.meta import Session
13 from rhodecode.model.notification import NotificationModel
13 from rhodecode.model.notification import NotificationModel
14 from rhodecode.model.users_group import UsersGroupModel
14 from rhodecode.model.users_group import UsersGroupModel
15 from rhodecode.lib.auth import AuthUser
16
17
18 def _make_group(path, desc='desc', parent_id=None,
19 skip_if_exists=False):
20
21 gr = RepoGroup.get_by_group_name(path)
22 if gr and skip_if_exists:
23 return gr
24
25 gr = ReposGroupModel().create(path, desc, parent_id)
26 Session.commit()
27 return gr
28
15
29
16 class TestReposGroups(unittest.TestCase):
30 class TestReposGroups(unittest.TestCase):
17
31
18 def setUp(self):
32 def setUp(self):
19 self.g1 = self.__make_group('test1', skip_if_exists=True)
33 self.g1 = _make_group('test1', skip_if_exists=True)
20 self.g2 = self.__make_group('test2', skip_if_exists=True)
34 self.g2 = _make_group('test2', skip_if_exists=True)
21 self.g3 = self.__make_group('test3', skip_if_exists=True)
35 self.g3 = _make_group('test3', skip_if_exists=True)
22
36
23 def tearDown(self):
37 def tearDown(self):
24 print 'out'
38 print 'out'
25
39
26 def __check_path(self, *path):
40 def __check_path(self, *path):
27 path = [TESTS_TMP_PATH] + list(path)
41 path = [TESTS_TMP_PATH] + list(path)
28 path = os.path.join(*path)
42 path = os.path.join(*path)
29 return os.path.isdir(path)
43 return os.path.isdir(path)
30
44
31 def _check_folders(self):
45 def _check_folders(self):
32 print os.listdir(TESTS_TMP_PATH)
46 print os.listdir(TESTS_TMP_PATH)
33
47
34 def __make_group(self, path, desc='desc', parent_id=None,
35 skip_if_exists=False):
36
37 gr = RepoGroup.get_by_group_name(path)
38 if gr and skip_if_exists:
39 return gr
40
41 form_data = dict(group_name=path,
42 group_description=desc,
43 group_parent_id=parent_id)
44 gr = ReposGroupModel().create(form_data)
45 Session.commit()
46 return gr
47
48 def __delete_group(self, id_):
48 def __delete_group(self, id_):
49 ReposGroupModel().delete(id_)
49 ReposGroupModel().delete(id_)
50
50
51
52 def __update_group(self, id_, path, desc='desc', parent_id=None):
51 def __update_group(self, id_, path, desc='desc', parent_id=None):
53 form_data = dict(group_name=path,
52 form_data = dict(group_name=path,
54 group_description=desc,
53 group_description=desc,
55 group_parent_id=parent_id)
54 group_parent_id=parent_id,
55 perms_updates=[],
56 perms_new=[])
56
57
57 gr = ReposGroupModel().update(id_, form_data)
58 gr = ReposGroupModel().update(id_, form_data)
58 return gr
59 return gr
59
60
60 def test_create_group(self):
61 def test_create_group(self):
61 g = self.__make_group('newGroup')
62 g = _make_group('newGroup')
62 self.assertEqual(g.full_path, 'newGroup')
63 self.assertEqual(g.full_path, 'newGroup')
63
64
64 self.assertTrue(self.__check_path('newGroup'))
65 self.assertTrue(self.__check_path('newGroup'))
65
66
66
67 def test_create_same_name_group(self):
67 def test_create_same_name_group(self):
68 self.assertRaises(IntegrityError, lambda:self.__make_group('newGroup'))
68 self.assertRaises(IntegrityError, lambda:_make_group('newGroup'))
69 Session.rollback()
69 Session.rollback()
70
70
71 def test_same_subgroup(self):
71 def test_same_subgroup(self):
72 sg1 = self.__make_group('sub1', parent_id=self.g1.group_id)
72 sg1 = _make_group('sub1', parent_id=self.g1.group_id)
73 self.assertEqual(sg1.parent_group, self.g1)
73 self.assertEqual(sg1.parent_group, self.g1)
74 self.assertEqual(sg1.full_path, 'test1/sub1')
74 self.assertEqual(sg1.full_path, 'test1/sub1')
75 self.assertTrue(self.__check_path('test1', 'sub1'))
75 self.assertTrue(self.__check_path('test1', 'sub1'))
76
76
77 ssg1 = self.__make_group('subsub1', parent_id=sg1.group_id)
77 ssg1 = _make_group('subsub1', parent_id=sg1.group_id)
78 self.assertEqual(ssg1.parent_group, sg1)
78 self.assertEqual(ssg1.parent_group, sg1)
79 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
79 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
80 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
80 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
81
81
82
83 def test_remove_group(self):
82 def test_remove_group(self):
84 sg1 = self.__make_group('deleteme')
83 sg1 = _make_group('deleteme')
85 self.__delete_group(sg1.group_id)
84 self.__delete_group(sg1.group_id)
86
85
87 self.assertEqual(RepoGroup.get(sg1.group_id), None)
86 self.assertEqual(RepoGroup.get(sg1.group_id), None)
88 self.assertFalse(self.__check_path('deteteme'))
87 self.assertFalse(self.__check_path('deteteme'))
89
88
90 sg1 = self.__make_group('deleteme', parent_id=self.g1.group_id)
89 sg1 = _make_group('deleteme', parent_id=self.g1.group_id)
91 self.__delete_group(sg1.group_id)
90 self.__delete_group(sg1.group_id)
92
91
93 self.assertEqual(RepoGroup.get(sg1.group_id), None)
92 self.assertEqual(RepoGroup.get(sg1.group_id), None)
94 self.assertFalse(self.__check_path('test1', 'deteteme'))
93 self.assertFalse(self.__check_path('test1', 'deteteme'))
95
94
96
97 def test_rename_single_group(self):
95 def test_rename_single_group(self):
98 sg1 = self.__make_group('initial')
96 sg1 = _make_group('initial')
99
97
100 new_sg1 = self.__update_group(sg1.group_id, 'after')
98 new_sg1 = self.__update_group(sg1.group_id, 'after')
101 self.assertTrue(self.__check_path('after'))
99 self.assertTrue(self.__check_path('after'))
102 self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
100 self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
103
101
104
105 def test_update_group_parent(self):
102 def test_update_group_parent(self):
106
103
107 sg1 = self.__make_group('initial', parent_id=self.g1.group_id)
104 sg1 = _make_group('initial', parent_id=self.g1.group_id)
108
105
109 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
106 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
110 self.assertTrue(self.__check_path('test1', 'after'))
107 self.assertTrue(self.__check_path('test1', 'after'))
111 self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
108 self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
112
109
113
114 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
110 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
115 self.assertTrue(self.__check_path('test3', 'after'))
111 self.assertTrue(self.__check_path('test3', 'after'))
116 self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
112 self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
117
113
118
119 new_sg1 = self.__update_group(sg1.group_id, 'hello')
114 new_sg1 = self.__update_group(sg1.group_id, 'hello')
120 self.assertTrue(self.__check_path('hello'))
115 self.assertTrue(self.__check_path('hello'))
121
116
122 self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
117 self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
123
118
124
125
126 def test_subgrouping_with_repo(self):
119 def test_subgrouping_with_repo(self):
127
120
128 g1 = self.__make_group('g1')
121 g1 = _make_group('g1')
129 g2 = self.__make_group('g2')
122 g2 = _make_group('g2')
130
123
131 # create new repo
124 # create new repo
132 form_data = dict(repo_name='john',
125 form_data = dict(repo_name='john',
133 repo_name_full='john',
126 repo_name_full='john',
134 fork_name=None,
127 fork_name=None,
135 description=None,
128 description=None,
136 repo_group=None,
129 repo_group=None,
137 private=False,
130 private=False,
138 repo_type='hg',
131 repo_type='hg',
139 clone_uri=None)
132 clone_uri=None)
140 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
133 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
141 r = RepoModel().create(form_data, cur_user)
134 r = RepoModel().create(form_data, cur_user)
142
135
143 self.assertEqual(r.repo_name, 'john')
136 self.assertEqual(r.repo_name, 'john')
144
137
145 # put repo into group
138 # put repo into group
146 form_data = form_data
139 form_data = form_data
147 form_data['repo_group'] = g1.group_id
140 form_data['repo_group'] = g1.group_id
148 form_data['perms_new'] = []
141 form_data['perms_new'] = []
149 form_data['perms_updates'] = []
142 form_data['perms_updates'] = []
150 RepoModel().update(r.repo_name, form_data)
143 RepoModel().update(r.repo_name, form_data)
151 self.assertEqual(r.repo_name, 'g1/john')
144 self.assertEqual(r.repo_name, 'g1/john')
152
145
153
154 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
146 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
155 self.assertTrue(self.__check_path('g2', 'g1'))
147 self.assertTrue(self.__check_path('g2', 'g1'))
156
148
157 # test repo
149 # test repo
158 self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
150 self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
159
151
152
160 class TestUser(unittest.TestCase):
153 class TestUser(unittest.TestCase):
161 def __init__(self, methodName='runTest'):
154 def __init__(self, methodName='runTest'):
162 Session.remove()
155 Session.remove()
163 super(TestUser, self).__init__(methodName=methodName)
156 super(TestUser, self).__init__(methodName=methodName)
164
157
165 def test_create_and_remove(self):
158 def test_create_and_remove(self):
166 usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe',
159 usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe',
167 email=u'u232@rhodecode.org',
160 email=u'u232@rhodecode.org',
168 name=u'u1', lastname=u'u1')
161 name=u'u1', lastname=u'u1')
169 Session.commit()
162 Session.commit()
170 self.assertEqual(User.get_by_username(u'test_user'), usr)
163 self.assertEqual(User.get_by_username(u'test_user'), usr)
171
164
172 # make users group
165 # make users group
173 users_group = UsersGroupModel().create('some_example_group')
166 users_group = UsersGroupModel().create('some_example_group')
174 Session.commit()
167 Session.commit()
175
168
176 UsersGroupModel().add_user_to_group(users_group, usr)
169 UsersGroupModel().add_user_to_group(users_group, usr)
177 Session.commit()
170 Session.commit()
178
171
179 self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
172 self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
180 self.assertEqual(UsersGroupMember.query().count(), 1)
173 self.assertEqual(UsersGroupMember.query().count(), 1)
181 UserModel().delete(usr.user_id)
174 UserModel().delete(usr.user_id)
182 Session.commit()
175 Session.commit()
183
176
184 self.assertEqual(UsersGroupMember.query().all(), [])
177 self.assertEqual(UsersGroupMember.query().all(), [])
185
178
186
179
187 class TestNotifications(unittest.TestCase):
180 class TestNotifications(unittest.TestCase):
188
181
189 def __init__(self, methodName='runTest'):
182 def __init__(self, methodName='runTest'):
190 Session.remove()
183 Session.remove()
191 self.u1 = UserModel().create_or_update(username=u'u1',
184 self.u1 = UserModel().create_or_update(username=u'u1',
192 password=u'qweqwe',
185 password=u'qweqwe',
193 email=u'u1@rhodecode.org',
186 email=u'u1@rhodecode.org',
194 name=u'u1', lastname=u'u1')
187 name=u'u1', lastname=u'u1')
195 Session.commit()
188 Session.commit()
196 self.u1 = self.u1.user_id
189 self.u1 = self.u1.user_id
197
190
198 self.u2 = UserModel().create_or_update(username=u'u2',
191 self.u2 = UserModel().create_or_update(username=u'u2',
199 password=u'qweqwe',
192 password=u'qweqwe',
200 email=u'u2@rhodecode.org',
193 email=u'u2@rhodecode.org',
201 name=u'u2', lastname=u'u3')
194 name=u'u2', lastname=u'u3')
202 Session.commit()
195 Session.commit()
203 self.u2 = self.u2.user_id
196 self.u2 = self.u2.user_id
204
197
205 self.u3 = UserModel().create_or_update(username=u'u3',
198 self.u3 = UserModel().create_or_update(username=u'u3',
206 password=u'qweqwe',
199 password=u'qweqwe',
207 email=u'u3@rhodecode.org',
200 email=u'u3@rhodecode.org',
208 name=u'u3', lastname=u'u3')
201 name=u'u3', lastname=u'u3')
209 Session.commit()
202 Session.commit()
210 self.u3 = self.u3.user_id
203 self.u3 = self.u3.user_id
211
204
212 super(TestNotifications, self).__init__(methodName=methodName)
205 super(TestNotifications, self).__init__(methodName=methodName)
213
206
214 def _clean_notifications(self):
207 def _clean_notifications(self):
215 for n in Notification.query().all():
208 for n in Notification.query().all():
216 Session.delete(n)
209 Session.delete(n)
217
210
218 Session.commit()
211 Session.commit()
219 self.assertEqual(Notification.query().all(), [])
212 self.assertEqual(Notification.query().all(), [])
220
213
221 def tearDown(self):
214 def tearDown(self):
222 self._clean_notifications()
215 self._clean_notifications()
223
216
224 def test_create_notification(self):
217 def test_create_notification(self):
225 self.assertEqual([], Notification.query().all())
218 self.assertEqual([], Notification.query().all())
226 self.assertEqual([], UserNotification.query().all())
219 self.assertEqual([], UserNotification.query().all())
227
220
228 usrs = [self.u1, self.u2]
221 usrs = [self.u1, self.u2]
229 notification = NotificationModel().create(created_by=self.u1,
222 notification = NotificationModel().create(created_by=self.u1,
230 subject=u'subj', body=u'hi there',
223 subject=u'subj', body=u'hi there',
231 recipients=usrs)
224 recipients=usrs)
232 Session.commit()
225 Session.commit()
233 u1 = User.get(self.u1)
226 u1 = User.get(self.u1)
234 u2 = User.get(self.u2)
227 u2 = User.get(self.u2)
235 u3 = User.get(self.u3)
228 u3 = User.get(self.u3)
236 notifications = Notification.query().all()
229 notifications = Notification.query().all()
237 self.assertEqual(len(notifications), 1)
230 self.assertEqual(len(notifications), 1)
238
231
239 unotification = UserNotification.query()\
232 unotification = UserNotification.query()\
240 .filter(UserNotification.notification == notification).all()
233 .filter(UserNotification.notification == notification).all()
241
234
242 self.assertEqual(notifications[0].recipients, [u1, u2])
235 self.assertEqual(notifications[0].recipients, [u1, u2])
243 self.assertEqual(notification.notification_id,
236 self.assertEqual(notification.notification_id,
244 notifications[0].notification_id)
237 notifications[0].notification_id)
245 self.assertEqual(len(unotification), len(usrs))
238 self.assertEqual(len(unotification), len(usrs))
246 self.assertEqual([x.user.user_id for x in unotification], usrs)
239 self.assertEqual([x.user.user_id for x in unotification], usrs)
247
240
248
249 def test_user_notifications(self):
241 def test_user_notifications(self):
250 self.assertEqual([], Notification.query().all())
242 self.assertEqual([], Notification.query().all())
251 self.assertEqual([], UserNotification.query().all())
243 self.assertEqual([], UserNotification.query().all())
252
244
253 notification1 = NotificationModel().create(created_by=self.u1,
245 notification1 = NotificationModel().create(created_by=self.u1,
254 subject=u'subj', body=u'hi there1',
246 subject=u'subj', body=u'hi there1',
255 recipients=[self.u3])
247 recipients=[self.u3])
256 Session.commit()
248 Session.commit()
257 notification2 = NotificationModel().create(created_by=self.u1,
249 notification2 = NotificationModel().create(created_by=self.u1,
258 subject=u'subj', body=u'hi there2',
250 subject=u'subj', body=u'hi there2',
259 recipients=[self.u3])
251 recipients=[self.u3])
260 Session.commit()
252 Session.commit()
261 u3 = Session.query(User).get(self.u3)
253 u3 = Session.query(User).get(self.u3)
262
254
263 self.assertEqual(sorted([x.notification for x in u3.notifications]),
255 self.assertEqual(sorted([x.notification for x in u3.notifications]),
264 sorted([notification2, notification1]))
256 sorted([notification2, notification1]))
265
257
266 def test_delete_notifications(self):
258 def test_delete_notifications(self):
267 self.assertEqual([], Notification.query().all())
259 self.assertEqual([], Notification.query().all())
268 self.assertEqual([], UserNotification.query().all())
260 self.assertEqual([], UserNotification.query().all())
269
261
270 notification = NotificationModel().create(created_by=self.u1,
262 notification = NotificationModel().create(created_by=self.u1,
271 subject=u'title', body=u'hi there3',
263 subject=u'title', body=u'hi there3',
272 recipients=[self.u3, self.u1, self.u2])
264 recipients=[self.u3, self.u1, self.u2])
273 Session.commit()
265 Session.commit()
274 notifications = Notification.query().all()
266 notifications = Notification.query().all()
275 self.assertTrue(notification in notifications)
267 self.assertTrue(notification in notifications)
276
268
277 Notification.delete(notification.notification_id)
269 Notification.delete(notification.notification_id)
278 Session.commit()
270 Session.commit()
279
271
280 notifications = Notification.query().all()
272 notifications = Notification.query().all()
281 self.assertFalse(notification in notifications)
273 self.assertFalse(notification in notifications)
282
274
283 un = UserNotification.query().filter(UserNotification.notification
275 un = UserNotification.query().filter(UserNotification.notification
284 == notification).all()
276 == notification).all()
285 self.assertEqual(un, [])
277 self.assertEqual(un, [])
286
278
287
288 def test_delete_association(self):
279 def test_delete_association(self):
289
280
290 self.assertEqual([], Notification.query().all())
281 self.assertEqual([], Notification.query().all())
291 self.assertEqual([], UserNotification.query().all())
282 self.assertEqual([], UserNotification.query().all())
292
283
293 notification = NotificationModel().create(created_by=self.u1,
284 notification = NotificationModel().create(created_by=self.u1,
294 subject=u'title', body=u'hi there3',
285 subject=u'title', body=u'hi there3',
295 recipients=[self.u3, self.u1, self.u2])
286 recipients=[self.u3, self.u1, self.u2])
296 Session.commit()
287 Session.commit()
297
288
298 unotification = UserNotification.query()\
289 unotification = UserNotification.query()\
299 .filter(UserNotification.notification ==
290 .filter(UserNotification.notification ==
300 notification)\
291 notification)\
301 .filter(UserNotification.user_id == self.u3)\
292 .filter(UserNotification.user_id == self.u3)\
302 .scalar()
293 .scalar()
303
294
304 self.assertEqual(unotification.user_id, self.u3)
295 self.assertEqual(unotification.user_id, self.u3)
305
296
306 NotificationModel().delete(self.u3,
297 NotificationModel().delete(self.u3,
307 notification.notification_id)
298 notification.notification_id)
308 Session.commit()
299 Session.commit()
309
300
310 u3notification = UserNotification.query()\
301 u3notification = UserNotification.query()\
311 .filter(UserNotification.notification ==
302 .filter(UserNotification.notification ==
312 notification)\
303 notification)\
313 .filter(UserNotification.user_id == self.u3)\
304 .filter(UserNotification.user_id == self.u3)\
314 .scalar()
305 .scalar()
315
306
316 self.assertEqual(u3notification, None)
307 self.assertEqual(u3notification, None)
317
308
318 # notification object is still there
309 # notification object is still there
319 self.assertEqual(Notification.query().all(), [notification])
310 self.assertEqual(Notification.query().all(), [notification])
320
311
321 #u1 and u2 still have assignments
312 #u1 and u2 still have assignments
322 u1notification = UserNotification.query()\
313 u1notification = UserNotification.query()\
323 .filter(UserNotification.notification ==
314 .filter(UserNotification.notification ==
324 notification)\
315 notification)\
325 .filter(UserNotification.user_id == self.u1)\
316 .filter(UserNotification.user_id == self.u1)\
326 .scalar()
317 .scalar()
327 self.assertNotEqual(u1notification, None)
318 self.assertNotEqual(u1notification, None)
328 u2notification = UserNotification.query()\
319 u2notification = UserNotification.query()\
329 .filter(UserNotification.notification ==
320 .filter(UserNotification.notification ==
330 notification)\
321 notification)\
331 .filter(UserNotification.user_id == self.u2)\
322 .filter(UserNotification.user_id == self.u2)\
332 .scalar()
323 .scalar()
333 self.assertNotEqual(u2notification, None)
324 self.assertNotEqual(u2notification, None)
334
325
335 def test_notification_counter(self):
326 def test_notification_counter(self):
336 self._clean_notifications()
327 self._clean_notifications()
337 self.assertEqual([], Notification.query().all())
328 self.assertEqual([], Notification.query().all())
338 self.assertEqual([], UserNotification.query().all())
329 self.assertEqual([], UserNotification.query().all())
339
330
340 NotificationModel().create(created_by=self.u1,
331 NotificationModel().create(created_by=self.u1,
341 subject=u'title', body=u'hi there_delete',
332 subject=u'title', body=u'hi there_delete',
342 recipients=[self.u3, self.u1])
333 recipients=[self.u3, self.u1])
343 Session.commit()
334 Session.commit()
344
335
345 self.assertEqual(NotificationModel()
336 self.assertEqual(NotificationModel()
346 .get_unread_cnt_for_user(self.u1), 1)
337 .get_unread_cnt_for_user(self.u1), 1)
347 self.assertEqual(NotificationModel()
338 self.assertEqual(NotificationModel()
348 .get_unread_cnt_for_user(self.u2), 0)
339 .get_unread_cnt_for_user(self.u2), 0)
349 self.assertEqual(NotificationModel()
340 self.assertEqual(NotificationModel()
350 .get_unread_cnt_for_user(self.u3), 1)
341 .get_unread_cnt_for_user(self.u3), 1)
351
342
352 notification = NotificationModel().create(created_by=self.u1,
343 notification = NotificationModel().create(created_by=self.u1,
353 subject=u'title', body=u'hi there3',
344 subject=u'title', body=u'hi there3',
354 recipients=[self.u3, self.u1, self.u2])
345 recipients=[self.u3, self.u1, self.u2])
355 Session.commit()
346 Session.commit()
356
347
357 self.assertEqual(NotificationModel()
348 self.assertEqual(NotificationModel()
358 .get_unread_cnt_for_user(self.u1), 2)
349 .get_unread_cnt_for_user(self.u1), 2)
359 self.assertEqual(NotificationModel()
350 self.assertEqual(NotificationModel()
360 .get_unread_cnt_for_user(self.u2), 1)
351 .get_unread_cnt_for_user(self.u2), 1)
361 self.assertEqual(NotificationModel()
352 self.assertEqual(NotificationModel()
362 .get_unread_cnt_for_user(self.u3), 2)
353 .get_unread_cnt_for_user(self.u3), 2)
363
354
355
364 class TestUsers(unittest.TestCase):
356 class TestUsers(unittest.TestCase):
365
357
366 def __init__(self, methodName='runTest'):
358 def __init__(self, methodName='runTest'):
367 super(TestUsers, self).__init__(methodName=methodName)
359 super(TestUsers, self).__init__(methodName=methodName)
368
360
369 def setUp(self):
361 def setUp(self):
370 self.u1 = UserModel().create_or_update(username=u'u1',
362 self.u1 = UserModel().create_or_update(username=u'u1',
371 password=u'qweqwe',
363 password=u'qweqwe',
372 email=u'u1@rhodecode.org',
364 email=u'u1@rhodecode.org',
373 name=u'u1', lastname=u'u1')
365 name=u'u1', lastname=u'u1')
374
366
375 def tearDown(self):
367 def tearDown(self):
376 perm = Permission.query().all()
368 perm = Permission.query().all()
377 for p in perm:
369 for p in perm:
378 UserModel().revoke_perm(self.u1, p)
370 UserModel().revoke_perm(self.u1, p)
379
371
380 UserModel().delete(self.u1)
372 UserModel().delete(self.u1)
381 Session.commit()
373 Session.commit()
382
374
383 def test_add_perm(self):
375 def test_add_perm(self):
384 perm = Permission.query().all()[0]
376 perm = Permission.query().all()[0]
385 UserModel().grant_perm(self.u1, perm)
377 UserModel().grant_perm(self.u1, perm)
386 Session.commit()
378 Session.commit()
387 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
379 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
388
380
389 def test_has_perm(self):
381 def test_has_perm(self):
390 perm = Permission.query().all()
382 perm = Permission.query().all()
391 for p in perm:
383 for p in perm:
392 has_p = UserModel().has_perm(self.u1, p)
384 has_p = UserModel().has_perm(self.u1, p)
393 self.assertEqual(False, has_p)
385 self.assertEqual(False, has_p)
394
386
395 def test_revoke_perm(self):
387 def test_revoke_perm(self):
396 perm = Permission.query().all()[0]
388 perm = Permission.query().all()[0]
397 UserModel().grant_perm(self.u1, perm)
389 UserModel().grant_perm(self.u1, perm)
398 Session.commit()
390 Session.commit()
399 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
391 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
400
392
401 #revoke
393 #revoke
402 UserModel().revoke_perm(self.u1, perm)
394 UserModel().revoke_perm(self.u1, perm)
403 Session.commit()
395 Session.commit()
404 self.assertEqual(UserModel().has_perm(self.u1, perm),False)
396 self.assertEqual(UserModel().has_perm(self.u1, perm), False)
397
398
399 class TestPermissions(unittest.TestCase):
400 def __init__(self, methodName='runTest'):
401 super(TestPermissions, self).__init__(methodName=methodName)
402
403 def setUp(self):
404 self.u1 = UserModel().create_or_update(
405 username=u'u1', password=u'qweqwe',
406 email=u'u1@rhodecode.org', name=u'u1', lastname=u'u1'
407 )
408 self.a1 = UserModel().create_or_update(
409 username=u'a1', password=u'qweqwe',
410 email=u'a1@rhodecode.org', name=u'a1', lastname=u'a1', admin=True
411 )
412 Session.commit()
413
414 def tearDown(self):
415 UserModel().delete(self.u1)
416 UserModel().delete(self.a1)
417 if hasattr(self, 'g1'):
418 ReposGroupModel().delete(self.g1.group_id)
419 if hasattr(self, 'g2'):
420 ReposGroupModel().delete(self.g2.group_id)
421
422 if hasattr(self, 'ug1'):
423 UsersGroupModel().delete(self.ug1, force=True)
424
425 Session.commit()
426
427 def test_default_perms_set(self):
428 u1_auth = AuthUser(user_id=self.u1.user_id)
429 perms = {
430 'repositories_groups': {},
431 'global': set([u'hg.create.repository', u'repository.read',
432 u'hg.register.manual_activate']),
433 'repositories': {u'vcs_test_hg': u'repository.read'}
434 }
435 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
436 perms['repositories'][HG_REPO])
437 new_perm = 'repository.write'
438 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
439 Session.commit()
440
441 u1_auth = AuthUser(user_id=self.u1.user_id)
442 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], new_perm)
443
444 def test_default_admin_perms_set(self):
445 a1_auth = AuthUser(user_id=self.a1.user_id)
446 perms = {
447 'repositories_groups': {},
448 'global': set([u'hg.admin']),
449 'repositories': {u'vcs_test_hg': u'repository.admin'}
450 }
451 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
452 perms['repositories'][HG_REPO])
453 new_perm = 'repository.write'
454 RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1, perm=new_perm)
455 Session.commit()
456 # cannot really downgrade admins permissions !? they still get's set as
457 # admin !
458 u1_auth = AuthUser(user_id=self.a1.user_id)
459 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
460 perms['repositories'][HG_REPO])
461
462 def test_default_group_perms(self):
463 self.g1 = _make_group('test1', skip_if_exists=True)
464 self.g2 = _make_group('test2', skip_if_exists=True)
465 u1_auth = AuthUser(user_id=self.u1.user_id)
466 perms = {
467 'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
468 'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']),
469 'repositories': {u'vcs_test_hg': u'repository.read'}
470 }
471 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
472 perms['repositories'][HG_REPO])
473 self.assertEqual(u1_auth.permissions['repositories_groups'],
474 perms['repositories_groups'])
475
476 def test_default_admin_group_perms(self):
477 self.g1 = _make_group('test1', skip_if_exists=True)
478 self.g2 = _make_group('test2', skip_if_exists=True)
479 a1_auth = AuthUser(user_id=self.a1.user_id)
480 perms = {
481 'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
482 'global': set(['hg.admin']),
483 'repositories': {u'vcs_test_hg': 'repository.admin'}
484 }
485
486 self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
487 perms['repositories'][HG_REPO])
488 self.assertEqual(a1_auth.permissions['repositories_groups'],
489 perms['repositories_groups'])
490
491 def test_propagated_permission_from_users_group(self):
492 # make group
493 self.ug1 = UsersGroupModel().create('G1')
494 # add user to group
495 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
496
497 # set permission to lower
498 new_perm = 'repository.none'
499 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
500 Session.commit()
501 u1_auth = AuthUser(user_id=self.u1.user_id)
502 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
503 new_perm)
504
505 # grant perm for group this should override permission from user
506 new_perm = 'repository.write'
507 RepoModel().grant_users_group_permission(repo=HG_REPO,
508 group_name=self.ug1,
509 perm=new_perm)
510 # check perms
511 u1_auth = AuthUser(user_id=self.u1.user_id)
512 perms = {
513 'repositories_groups': {},
514 'global': set([u'hg.create.repository', u'repository.read',
515 u'hg.register.manual_activate']),
516 'repositories': {u'vcs_test_hg': u'repository.read'}
517 }
518 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
519 new_perm)
520 self.assertEqual(u1_auth.permissions['repositories_groups'],
521 perms['repositories_groups'])
522
523 def test_propagated_permission_from_users_group_lower_weight(self):
524 # make group
525 self.ug1 = UsersGroupModel().create('G1')
526 # add user to group
527 UsersGroupModel().add_user_to_group(self.ug1, self.u1)
528
529 # set permission to lower
530 new_perm_h = 'repository.write'
531 RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
532 perm=new_perm_h)
533 Session.commit()
534 u1_auth = AuthUser(user_id=self.u1.user_id)
535 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
536 new_perm_h)
537
538 # grant perm for group this should NOT override permission from user
539 # since it's lower than granted
540 new_perm_l = 'repository.read'
541 RepoModel().grant_users_group_permission(repo=HG_REPO,
542 group_name=self.ug1,
543 perm=new_perm_l)
544 # check perms
545 u1_auth = AuthUser(user_id=self.u1.user_id)
546 perms = {
547 'repositories_groups': {},
548 'global': set([u'hg.create.repository', u'repository.read',
549 u'hg.register.manual_activate']),
550 'repositories': {u'vcs_test_hg': u'repository.write'}
551 }
552 self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
553 new_perm_h)
554 self.assertEqual(u1_auth.permissions['repositories_groups'],
555 perms['repositories_groups'])
@@ -1,229 +1,229 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
13 ## any error reports after application crash ##
13 ## any error reports after application crash ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20
20
21 #smtp_server = mail.server.com
21 #smtp_server = mail.server.com
22 #smtp_username =
22 #smtp_username =
23 #smtp_password =
23 #smtp_password =
24 #smtp_port =
24 #smtp_port =
25 #smtp_use_tls = false
25 #smtp_use_tls = false
26 #smtp_use_ssl = true
26 #smtp_use_ssl = true
27
27
28 [server:main]
28 [server:main]
29 ##nr of threads to spawn
29 ##nr of threads to spawn
30 threadpool_workers = 5
30 threadpool_workers = 5
31
31
32 ##max request before thread respawn
32 ##max request before thread respawn
33 threadpool_max_requests = 2
33 threadpool_max_requests = 2
34
34
35 ##option to use threads of process
35 ##option to use threads of process
36 use_threadpool = true
36 use_threadpool = true
37
37
38 use = egg:Paste#http
38 use = egg:Paste#http
39 host = 127.0.0.1
39 host = 127.0.0.1
40 port = 5000
40 port = 5000
41
41
42 [app:main]
42 [app:main]
43 use = egg:rhodecode
43 use = egg:rhodecode
44 full_stack = true
44 full_stack = true
45 static_files = true
45 static_files = true
46 lang=en
46 lang=en
47 cache_dir = /tmp/data
47 cache_dir = /tmp/data
48 index_dir = /tmp/index
48 index_dir = /tmp/index
49 app_instance_uuid = develop-test
49 app_instance_uuid = develop-test
50 cut_off_limit = 256000
50 cut_off_limit = 256000
51 force_https = false
51 force_https = false
52 commit_parse_limit = 25
52 commit_parse_limit = 25
53 use_gravatar = true
53 use_gravatar = true
54 container_auth_enabled = false
54 container_auth_enabled = false
55 proxypass_auth_enabled = false
55 proxypass_auth_enabled = false
56
56
57 ####################################
57 ####################################
58 ### CELERY CONFIG ####
58 ### CELERY CONFIG ####
59 ####################################
59 ####################################
60 use_celery = false
60 use_celery = false
61 broker.host = localhost
61 broker.host = localhost
62 broker.vhost = rabbitmqhost
62 broker.vhost = rabbitmqhost
63 broker.port = 5672
63 broker.port = 5672
64 broker.user = rabbitmq
64 broker.user = rabbitmq
65 broker.password = qweqwe
65 broker.password = qweqwe
66
66
67 celery.imports = rhodecode.lib.celerylib.tasks
67 celery.imports = rhodecode.lib.celerylib.tasks
68
68
69 celery.result.backend = amqp
69 celery.result.backend = amqp
70 celery.result.dburi = amqp://
70 celery.result.dburi = amqp://
71 celery.result.serialier = json
71 celery.result.serialier = json
72
72
73 #celery.send.task.error.emails = true
73 #celery.send.task.error.emails = true
74 #celery.amqp.task.result.expires = 18000
74 #celery.amqp.task.result.expires = 18000
75
75
76 celeryd.concurrency = 2
76 celeryd.concurrency = 2
77 #celeryd.log.file = celeryd.log
77 #celeryd.log.file = celeryd.log
78 celeryd.log.level = debug
78 celeryd.log.level = debug
79 celeryd.max.tasks.per.child = 1
79 celeryd.max.tasks.per.child = 1
80
80
81 #tasks will never be sent to the queue, but executed locally instead.
81 #tasks will never be sent to the queue, but executed locally instead.
82 celery.always.eager = false
82 celery.always.eager = false
83
83
84 ####################################
84 ####################################
85 ### BEAKER CACHE ####
85 ### BEAKER CACHE ####
86 ####################################
86 ####################################
87 beaker.cache.data_dir=/tmp/data/cache/data
87 beaker.cache.data_dir=/tmp/data/cache/data
88 beaker.cache.lock_dir=/tmp/data/cache/lock
88 beaker.cache.lock_dir=/tmp/data/cache/lock
89 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
89 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
90
90
91 beaker.cache.super_short_term.type=memory
91 beaker.cache.super_short_term.type=memory
92 beaker.cache.super_short_term.expire=10
92 beaker.cache.super_short_term.expire=1
93 beaker.cache.super_short_term.key_length = 256
93 beaker.cache.super_short_term.key_length = 256
94
94
95 beaker.cache.short_term.type=memory
95 beaker.cache.short_term.type=memory
96 beaker.cache.short_term.expire=60
96 beaker.cache.short_term.expire=60
97 beaker.cache.short_term.key_length = 256
97 beaker.cache.short_term.key_length = 256
98
98
99 beaker.cache.long_term.type=memory
99 beaker.cache.long_term.type=memory
100 beaker.cache.long_term.expire=36000
100 beaker.cache.long_term.expire=36000
101 beaker.cache.long_term.key_length = 256
101 beaker.cache.long_term.key_length = 256
102
102
103 beaker.cache.sql_cache_short.type=memory
103 beaker.cache.sql_cache_short.type=memory
104 beaker.cache.sql_cache_short.expire=10
104 beaker.cache.sql_cache_short.expire=1
105 beaker.cache.sql_cache_short.key_length = 256
105 beaker.cache.sql_cache_short.key_length = 256
106
106
107 beaker.cache.sql_cache_med.type=memory
107 beaker.cache.sql_cache_med.type=memory
108 beaker.cache.sql_cache_med.expire=360
108 beaker.cache.sql_cache_med.expire=360
109 beaker.cache.sql_cache_med.key_length = 256
109 beaker.cache.sql_cache_med.key_length = 256
110
110
111 beaker.cache.sql_cache_long.type=file
111 beaker.cache.sql_cache_long.type=file
112 beaker.cache.sql_cache_long.expire=3600
112 beaker.cache.sql_cache_long.expire=3600
113 beaker.cache.sql_cache_long.key_length = 256
113 beaker.cache.sql_cache_long.key_length = 256
114
114
115 ####################################
115 ####################################
116 ### BEAKER SESSION ####
116 ### BEAKER SESSION ####
117 ####################################
117 ####################################
118 ## Type of storage used for the session, current types are
118 ## Type of storage used for the session, current types are
119 ## dbm, file, memcached, database, and memory.
119 ## dbm, file, memcached, database, and memory.
120 ## The storage uses the Container API
120 ## The storage uses the Container API
121 ##that is also used by the cache system.
121 ##that is also used by the cache system.
122 beaker.session.type = file
122 beaker.session.type = file
123
123
124 beaker.session.key = rhodecode
124 beaker.session.key = rhodecode
125 beaker.session.secret = g654dcno0-9873jhgfreyu
125 beaker.session.secret = g654dcno0-9873jhgfreyu
126 beaker.session.timeout = 36000
126 beaker.session.timeout = 36000
127
127
128 ##auto save the session to not to use .save()
128 ##auto save the session to not to use .save()
129 beaker.session.auto = False
129 beaker.session.auto = False
130
130
131 ##true exire at browser close
131 ##true exire at browser close
132 #beaker.session.cookie_expires = 3600
132 #beaker.session.cookie_expires = 3600
133
133
134
134
135 ################################################################################
135 ################################################################################
136 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
136 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
137 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
137 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
138 ## execute malicious code after an exception is raised. ##
138 ## execute malicious code after an exception is raised. ##
139 ################################################################################
139 ################################################################################
140 #set debug = false
140 #set debug = false
141
141
142 ##################################
142 ##################################
143 ### LOGVIEW CONFIG ###
143 ### LOGVIEW CONFIG ###
144 ##################################
144 ##################################
145 logview.sqlalchemy = #faa
145 logview.sqlalchemy = #faa
146 logview.pylons.templating = #bfb
146 logview.pylons.templating = #bfb
147 logview.pylons.util = #eee
147 logview.pylons.util = #eee
148
148
149 #########################################################
149 #########################################################
150 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
150 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
151 #########################################################
151 #########################################################
152 sqlalchemy.db1.url = sqlite:///%(here)s/test.db
152 sqlalchemy.db1.url = sqlite:///%(here)s/test.db
153 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
153 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
154 #sqlalchemy.db1.echo = false
154 #sqlalchemy.db1.echo = false
155 #sqlalchemy.db1.pool_recycle = 3600
155 #sqlalchemy.db1.pool_recycle = 3600
156 sqlalchemy.convert_unicode = true
156 sqlalchemy.convert_unicode = true
157
157
158 ################################
158 ################################
159 ### LOGGING CONFIGURATION ####
159 ### LOGGING CONFIGURATION ####
160 ################################
160 ################################
161 [loggers]
161 [loggers]
162 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
162 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
163
163
164 [handlers]
164 [handlers]
165 keys = console
165 keys = console
166
166
167 [formatters]
167 [formatters]
168 keys = generic, color_formatter
168 keys = generic, color_formatter
169
169
170 #############
170 #############
171 ## LOGGERS ##
171 ## LOGGERS ##
172 #############
172 #############
173 [logger_root]
173 [logger_root]
174 level = ERROR
174 level = ERROR
175 handlers = console
175 handlers = console
176
176
177 [logger_routes]
177 [logger_routes]
178 level = ERROR
178 level = ERROR
179 handlers =
179 handlers =
180 qualname = routes.middleware
180 qualname = routes.middleware
181 # "level = DEBUG" logs the route matched and routing variables.
181 # "level = DEBUG" logs the route matched and routing variables.
182 propagate = 1
182 propagate = 1
183
183
184 [logger_beaker]
184 [logger_beaker]
185 level = DEBUG
185 level = DEBUG
186 handlers =
186 handlers =
187 qualname = beaker.container
187 qualname = beaker.container
188 propagate = 1
188 propagate = 1
189
189
190 [logger_templates]
190 [logger_templates]
191 level = INFO
191 level = INFO
192 handlers =
192 handlers =
193 qualname = pylons.templating
193 qualname = pylons.templating
194 propagate = 1
194 propagate = 1
195
195
196 [logger_rhodecode]
196 [logger_rhodecode]
197 level = ERROR
197 level = ERROR
198 handlers =
198 handlers =
199 qualname = rhodecode
199 qualname = rhodecode
200 propagate = 1
200 propagate = 1
201
201
202 [logger_sqlalchemy]
202 [logger_sqlalchemy]
203 level = ERROR
203 level = ERROR
204 handlers = console
204 handlers = console
205 qualname = sqlalchemy.engine
205 qualname = sqlalchemy.engine
206 propagate = 0
206 propagate = 0
207
207
208 ##############
208 ##############
209 ## HANDLERS ##
209 ## HANDLERS ##
210 ##############
210 ##############
211
211
212 [handler_console]
212 [handler_console]
213 class = StreamHandler
213 class = StreamHandler
214 args = (sys.stderr,)
214 args = (sys.stderr,)
215 level = NOTSET
215 level = NOTSET
216 formatter = generic
216 formatter = generic
217
217
218 ################
218 ################
219 ## FORMATTERS ##
219 ## FORMATTERS ##
220 ################
220 ################
221
221
222 [formatter_generic]
222 [formatter_generic]
223 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
223 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
224 datefmt = %Y-%m-%d %H:%M:%S
224 datefmt = %Y-%m-%d %H:%M:%S
225
225
226 [formatter_color_formatter]
226 [formatter_color_formatter]
227 class=rhodecode.lib.colored_formatter.ColorFormatter
227 class=rhodecode.lib.colored_formatter.ColorFormatter
228 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
228 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
229 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
229 datefmt = %Y-%m-%d %H:%M:%S
General Comments 0
You need to be logged in to leave comments. Login now