##// 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 2 RhodeCode documentation!
3 3 ========================
4 4
5 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 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 9 simple API so it's easy integrable with existing external systems.
10 10
11 11 RhodeCode is similar in some respects to github or bitbucket_,
12 12 however RhodeCode can be run as standalone hosted application on your own server.
13 13 It is open source and donation ware and focuses more on providing a customized,
14 14 self administered interface for Mercurial and GIT repositories.
15 15 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
16 16 handle multiple different version control systems.
17 17
18 18 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
19 19
20 20 RhodeCode demo
21 21 --------------
22 22
23 23 http://demo.rhodecode.org
24 24
25 25 The default access is anonymous but you can login to an administrative account
26 26 using the following credentials:
27 27
28 28 - username: demo
29 29 - password: demo12
30 30
31 31 Source code
32 32 -----------
33 33
34 34 The latest sources can be obtained from official RhodeCode instance
35 35 https://secure.rhodecode.org
36 36
37 37
38 38 MIRRORS:
39 39
40 40 Issue tracker and sources at bitbucket_
41 41
42 42 http://bitbucket.org/marcinkuzminski/rhodecode
43 43
44 44 Sources at github_
45 45
46 46 https://github.com/marcinkuzminski/rhodecode
47 47
48 48 Installation
49 49 ------------
50 50
51 51 Please visit http://packages.python.org/RhodeCode/installation.html
52 52
53 53
54 54 RhodeCode Features
55 55 ------------------
56 56
57 57 - Has its own middleware to handle mercurial_ protocol requests.
58 58 Each request can be logged and authenticated.
59 59 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
60 60 Supports http/https and LDAP
61 61 - Full permissions (private/read/write/admin) and authentication per project.
62 62 One account for web interface and mercurial_ push/pull/clone operations.
63 63 - Have built in users groups for easier permission management
64 64 - Repository groups let you group repos and manage them easier.
65 65 - Users can fork other users repo. RhodeCode have also compare view to see
66 66 combined changeset for all changeset made within single push.
67 67 - Build in commit-api let's you add, edit and commit files right from RhodeCode
68 68 interface using simple editor or upload form for binaries.
69 69 - Mako templates let's you customize the look and feel of the application.
70 70 - Beautiful diffs, annotations and source code browsing all colored by pygments.
71 71 Raw diffs are made in git-diff format, including git_ binary-patches
72 72 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
73 73 - Admin interface with user/permission management. Admin activity journal, logs
74 74 pulls, pushes, forks, registrations and other actions made by all users.
75 75 - Server side forks. It is possible to fork a project and modify it freely
76 76 without breaking the main repository. You can even write Your own hooks
77 77 and install them
78 78 - code review with notification system, inline commenting, all parsed using
79 79 rst syntax
80 80 - rst and markdown README support for repositories
81 81 - Full text search powered by Whoosh on the source files, and file names.
82 82 Build in indexing daemons, with optional incremental index build
83 83 (no external search servers required all in one application)
84 84 - Setup project descriptions and info inside built in db for easy, non
85 85 file-system operations
86 86 - Intelligent cache with invalidation after push or project change, provides
87 87 high performance and always up to date data.
88 88 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
89 89 - Async tasks for speed and performance using celery_ (works without them too)
90 90 - Backup scripts can do backup of whole app and send it over scp to desired
91 91 location
92 92 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
93 93
94 94
95 95 .. include:: ./docs/screenshots.rst
96 96
97 97
98 98 Incoming / Plans
99 99 ----------------
100 100
101 101 - Finer granular permissions per branch, repo group or subrepo
102 102 - pull requests and web based merges
103 103 - per line file history
104 104 - SSH based authentication with server side key management
105 105 - Commit based built in wiki system
106 106 - More statistics and graph (global annotation + some more statistics)
107 107 - Other advancements as development continues (or you can of course make
108 108 additions and or requests)
109 109
110 110 License
111 111 -------
112 112
113 113 ``RhodeCode`` is released under the GPLv3 license.
114 114
115 115
116 116 Mailing group Q&A
117 117 -----------------
118 118
119 119 Join the `Google group <http://groups.google.com/group/rhodecode>`_
120 120
121 121 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
122 122
123 123 Join #rhodecode on FreeNode (irc.freenode.net)
124 124 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
125 125
126 126 Online documentation
127 127 --------------------
128 128
129 129 Online documentation for the current version of RhodeCode is available at
130 130 http://packages.python.org/RhodeCode/.
131 131 You may also build the documentation for yourself - go into ``docs/`` and run::
132 132
133 133 make html
134 134
135 135 (You need to have sphinx_ installed to build the documentation. If you don't
136 136 have sphinx_ installed you can install it via the command:
137 137 ``easy_install sphinx``)
138 138
139 139 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
140 140 .. _python: http://www.python.org/
141 141 .. _sphinx: http://sphinx.pocoo.org/
142 142 .. _mercurial: http://mercurial.selenic.com/
143 143 .. _bitbucket: http://bitbucket.org/
144 144 .. _github: http://github.com/
145 145 .. _subversion: http://subversion.tigris.org/
146 146 .. _git: http://git-scm.com/
147 147 .. _celery: http://celeryproject.org/
148 148 .. _Sphinx: http://sphinx.pocoo.org/
149 149 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
@@ -1,473 +1,541 b''
1 1 .. _api:
2 2
3 3
4 4 API
5 5 ===
6 6
7 7
8 8 Starting from RhodeCode version 1.2 a simple API was implemented.
9 9 There's a single schema for calling all api methods. API is implemented
10 10 with JSON protocol both ways. An url to send API request in RhodeCode is
11 11 <your_server>/_admin/api
12 12
13 13 API ACCESS FOR WEB VIEWS
14 14 ++++++++++++++++++++++++
15 15
16 16 API access can also be turned on for each web view in RhodeCode that is
17 17 decorated with `@LoginRequired` decorator. To enable API access simple change
18 18 the standard login decorator to `@LoginRequired(api_access=True)`.
19 19 After this change, a rhodecode view can be accessed without login by adding a
20 20 GET parameter `?api_key=<api_key>` to url. By default this is only
21 21 enabled on RSS/ATOM feed views.
22 22
23 23
24 24 API ACCESS
25 25 ++++++++++
26 26
27 27 All clients are required to send JSON-RPC spec JSON data::
28 28
29 29 {
30 30 "id:<id>,
31 31 "api_key":"<api_key>",
32 32 "method":"<method_name>",
33 33 "args":{"<arg_key>":"<arg_val>"}
34 34 }
35 35
36 36 Example call for autopulling remotes repos using curl::
37 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 39 Simply provide
40 40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
41 41 - *api_key* for access and permission validation.
42 42 - *method* is name of method to call
43 43 - *args* is an key:value list of arguments to pass to method
44 44
45 45 .. note::
46 46
47 47 api_key can be found in your user account page
48 48
49 49
50 50 RhodeCode API will return always a JSON-RPC response::
51 51
52 52 {
53 53 "id":<id>,
54 54 "result": "<result>",
55 55 "error": null
56 56 }
57 57
58 58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
59 59 calling api *error* key from response will contain failure description
60 60 and result will be null.
61 61
62 62 API METHODS
63 63 +++++++++++
64 64
65 65
66 66 pull
67 67 ----
68 68
69 69 Pulls given repo from remote location. Can be used to automatically keep
70 70 remote repos up to date. This command can be executed only using api_key
71 71 belonging to user with admin rights
72 72
73 73 INPUT::
74 74
75 75 api_key : "<api_key>"
76 76 method : "pull"
77 77 args : {
78 78 "repo_name" : "<reponame>"
79 79 }
80 80
81 81 OUTPUT::
82 82
83 83 result : "Pulled from <reponame>"
84 84 error : null
85 85
86 86
87 87 get_user
88 88 --------
89 89
90 90 Get's an user by username, Returns empty result if user is not found.
91 91 This command can be executed only using api_key belonging to user with admin
92 92 rights.
93 93
94
94 95 INPUT::
95 96
96 97 api_key : "<api_key>"
97 98 method : "get_user"
98 99 args : {
99 100 "username" : "<username>"
100 101 }
101 102
102 103 OUTPUT::
103 104
104 105 result: None if user does not exist or
105 106 {
106 107 "id" : "<id>",
107 108 "username" : "<username>",
108 109 "firstname": "<firstname>",
109 110 "lastname" : "<lastname>",
110 111 "email" : "<email>",
111 112 "active" : "<bool>",
112 113 "admin" :Β  "<bool>",
113 114 "ldap" : "<ldap_dn>"
114 115 }
115 116
116 117 error: null
117 118
118 119
119 120 get_users
120 121 ---------
121 122
122 123 Lists all existing users. This command can be executed only using api_key
123 124 belonging to user with admin rights.
124 125
126
125 127 INPUT::
126 128
127 129 api_key : "<api_key>"
128 130 method : "get_users"
129 131 args : { }
130 132
131 133 OUTPUT::
132 134
133 135 result: [
134 136 {
135 137 "id" : "<id>",
136 138 "username" : "<username>",
137 139 "firstname": "<firstname>",
138 140 "lastname" : "<lastname>",
139 141 "email" : "<email>",
140 142 "active" : "<bool>",
141 143 "admin" :Β  "<bool>",
142 144 "ldap" : "<ldap_dn>"
143 145 },
144 146 …
145 147 ]
146 148 error: null
147 149
150
148 151 create_user
149 152 -----------
150 153
151 154 Creates new user or updates current one if such user exists. This command can
152 155 be executed only using api_key belonging to user with admin rights.
153 156
157
154 158 INPUT::
155 159
156 160 api_key : "<api_key>"
157 161 method : "create_user"
158 162 args : {
159 163 "username" : "<username>",
160 164 "password" : "<password>",
161 165 "email" : "<useremail>",
162 166 "firstname" : "<firstname> = None",
163 167 "lastname" : "<lastname> = None",
164 168 "active" : "<bool> = True",
165 169 "admin" : "<bool> = False",
166 170 "ldap_dn" : "<ldap_dn> = None"
167 171 }
168 172
169 173 OUTPUT::
170 174
171 175 result: {
172 176 "id" : "<new_user_id>",
173 177 "msg" : "created new user <username>"
174 178 }
175 179 error: null
176 180
181
177 182 get_users_group
178 183 ---------------
179 184
180 185 Gets an existing users group. This command can be executed only using api_key
181 186 belonging to user with admin rights.
182 187
188
183 189 INPUT::
184 190
185 191 api_key : "<api_key>"
186 192 method : "get_users_group"
187 193 args : {
188 194 "group_name" : "<name>"
189 195 }
190 196
191 197 OUTPUT::
192 198
193 199 result : None if group not exist
194 200 {
195 201 "id" : "<id>",
196 202 "group_name" : "<groupname>",
197 203 "active": "<bool>",
198 204 "members" : [
199 205 { "id" : "<userid>",
200 206 "username" : "<username>",
201 207 "firstname": "<firstname>",
202 208 "lastname" : "<lastname>",
203 209 "email" : "<email>",
204 210 "active" : "<bool>",
205 211 "admin" :Β  "<bool>",
206 212 "ldap" : "<ldap_dn>"
207 213 },
208 214 …
209 215 ]
210 216 }
211 217 error : null
212 218
219
213 220 get_users_groups
214 221 ----------------
215 222
216 223 Lists all existing users groups. This command can be executed only using
217 224 api_key belonging to user with admin rights.
218 225
226
219 227 INPUT::
220 228
221 229 api_key : "<api_key>"
222 230 method : "get_users_groups"
223 231 args : { }
224 232
225 233 OUTPUT::
226 234
227 235 result : [
228 236 {
229 237 "id" : "<id>",
230 238 "group_name" : "<groupname>",
231 239 "active": "<bool>",
232 240 "members" : [
233 241 {
234 242 "id" : "<userid>",
235 243 "username" : "<username>",
236 244 "firstname": "<firstname>",
237 245 "lastname" : "<lastname>",
238 246 "email" : "<email>",
239 247 "active" : "<bool>",
240 248 "admin" :Β  "<bool>",
241 249 "ldap" : "<ldap_dn>"
242 250 },
243 251 …
244 252 ]
245 253 }
246 254 ]
247 255 error : null
248 256
249 257
250 258 create_users_group
251 259 ------------------
252 260
253 261 Creates new users group. This command can be executed only using api_key
254 262 belonging to user with admin rights
255 263
264
256 265 INPUT::
257 266
258 267 api_key : "<api_key>"
259 268 method : "create_users_group"
260 269 args: {
261 270 "group_name": "<groupname>",
262 271 "active":"<bool> = True"
263 272 }
264 273
265 274 OUTPUT::
266 275
267 276 result: {
268 277 "id": "<newusersgroupid>",
269 278 "msg": "created new users group <groupname>"
270 279 }
271 280 error: null
272 281
282
273 283 add_user_to_users_group
274 284 -----------------------
275 285
276 286 Adds a user to a users group. This command can be executed only using api_key
277 287 belonging to user with admin rights
278 288
289
279 290 INPUT::
280 291
281 292 api_key : "<api_key>"
282 293 method : "add_user_users_group"
283 294 args: {
284 295 "group_name" : "<groupname>",
285 296 "username" : "<username>"
286 297 }
287 298
288 299 OUTPUT::
289 300
290 301 result: {
291 302 "id": "<newusersgroupmemberid>",
292 303 "msg": "created new users group member"
293 304 }
294 305 error: null
295 306
307
296 308 get_repo
297 309 --------
298 310
299 311 Gets an existing repository. This command can be executed only using api_key
300 312 belonging to user with admin rights
301 313
314
302 315 INPUT::
303 316
304 317 api_key : "<api_key>"
305 318 method : "get_repo"
306 319 args: {
307 320 "repo_name" : "<reponame>"
308 321 }
309 322
310 323 OUTPUT::
311 324
312 325 result: None if repository does not exist or
313 326 {
314 327 "id" : "<id>",
315 328 "repo_name" : "<reponame>"
316 329 "type" : "<type>",
317 330 "description" : "<description>",
318 331 "members" : [
319 332 { "id" : "<userid>",
320 333 "username" : "<username>",
321 334 "firstname": "<firstname>",
322 335 "lastname" : "<lastname>",
323 336 "email" : "<email>",
324 337 "active" : "<bool>",
325 338 "admin" :Β  "<bool>",
326 339 "ldap" : "<ldap_dn>",
327 340 "permission" : "repository.(read|write|admin)"
328 341 },
329 342 …
330 343 {
331 344 "id" : "<usersgroupid>",
332 345 "name" : "<usersgroupname>",
333 346 "active": "<bool>",
334 347 "permission" : "repository.(read|write|admin)"
335 348 },
336 349 …
337 350 ]
338 351 }
339 352 error: null
340 353
354
341 355 get_repos
342 356 ---------
343 357
344 358 Lists all existing repositories. This command can be executed only using api_key
345 359 belonging to user with admin rights
346 360
361
347 362 INPUT::
348 363
349 364 api_key : "<api_key>"
350 365 method : "get_repos"
351 366 args: { }
352 367
353 368 OUTPUT::
354 369
355 370 result: [
356 371 {
357 372 "id" : "<id>",
358 373 "repo_name" : "<reponame>"
359 374 "type" : "<type>",
360 375 "description" : "<description>"
361 376 },
362 377 …
363 378 ]
364 379 error: null
365 380
366 381
367 382 get_repo_nodes
368 383 --------------
369 384
370 385 returns a list of nodes and it's children in a flat list for a given path
371 386 at given revision. It's possible to specify ret_type to show only `files` or
372 387 `dirs`. This command can be executed only using api_key belonging to user
373 388 with admin rights
374 389
390
375 391 INPUT::
376 392
377 393 api_key : "<api_key>"
378 394 method : "get_repo_nodes"
379 395 args: {
380 396 "repo_name" : "<reponame>",
381 397 "revision" : "<revision>",
382 398 "root_path" : "<root_path>",
383 399 "ret_type" : "<ret_type>" = 'all'
384 400 }
385 401
386 402 OUTPUT::
387 403
388 404 result: [
389 405 {
390 406 "name" : "<name>"
391 407 "type" : "<type>",
392 408 },
393 409 …
394 410 ]
395 411 error: null
396 412
397 413
398
399 414 create_repo
400 415 -----------
401 416
402 417 Creates a repository. This command can be executed only using api_key
403 418 belonging to user with admin rights.
404 419 If repository name contains "/", all needed repository groups will be created.
405 420 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
406 421 and create "baz" repository with "bar" as group.
407 422
423
408 424 INPUT::
409 425
410 426 api_key : "<api_key>"
411 427 method : "create_repo"
412 428 args: {
413 429 "repo_name" : "<reponame>",
414 430 "owner_name" : "<ownername>",
415 431 "description" : "<description> = ''",
416 432 "repo_type" : "<type> = 'hg'",
417 433 "private" : "<bool> = False"
418 434 }
419 435
420 436 OUTPUT::
421 437
422 438 result: {
423 "id": "<newrepoid>",
424 "msg": "Created new repository <reponame>",
439 "id": "<newrepoid>",
440 "msg": "Created new repository <reponame>",
425 441 }
426 442 error: null
427 443
428 add_user_to_repo
429 ----------------
444
445 grant_user_permission
446 ---------------------
430 447
431 Add a user to a repository. This command can be executed only using api_key
432 belonging to user with admin rights.
433 If "perm" is None, user will be removed from the repository.
448 Grant permission for user on given repository, or update existing one
449 if found. This command can be executed only using api_key belonging to user
450 with admin rights.
451
434 452
435 453 INPUT::
436 454
437 455 api_key : "<api_key>"
438 method : "add_user_to_repo"
456 method : "grant_user_permission"
439 457 args: {
440 458 "repo_name" : "<reponame>",
441 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 487 OUTPUT::
446 488
447 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 492 error: null
451 493
452 add_users_group_to_repo
453 -----------------------
494
495 grant_users_group_permission
496 ----------------------------
454 497
455 Add a users group to a repository. This command can be executed only using
456 api_key belonging to user with admin rights. If "perm" is None, group will
457 be removed from the repository.
498 Grant permission for users group on given repository, or update
499 existing one if found. This command can be executed only using
500 api_key belonging to user with admin rights.
501
458 502
459 503 INPUT::
460 504
461 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 531 args: {
464 532 "repo_name" : "<reponame>",
465 "group_name" : "<groupname>",
466 "perm" : "(None|repository.(read|write|admin))",
533 "users_group" : "<usersgroupname>",
467 534 }
535
468 536 OUTPUT::
469
537
470 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 1 """Pylons middleware initialization"""
2 2
3 3 from beaker.middleware import SessionMiddleware
4 4 from routes.middleware import RoutesMiddleware
5 5 from paste.cascade import Cascade
6 6 from paste.registry import RegistryManager
7 7 from paste.urlparser import StaticURLParser
8 8 from paste.deploy.converters import asbool
9 9 from paste.gzipper import make_gzip_middleware
10 10
11 11 from pylons.middleware import ErrorHandler, StatusCodeRedirect
12 12 from pylons.wsgiapp import PylonsApp
13 13
14 14 from rhodecode.lib.middleware.simplehg import SimpleHg
15 15 from rhodecode.lib.middleware.simplegit import SimpleGit
16 16 from rhodecode.lib.middleware.https_fixup import HttpsFixup
17 17 from rhodecode.config.environment import load_environment
18 18
19 19
20 20 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
21 21 """Create a Pylons WSGI application and return it
22 22
23 23 ``global_conf``
24 24 The inherited configuration for this application. Normally from
25 25 the [DEFAULT] section of the Paste ini file.
26 26
27 27 ``full_stack``
28 28 Whether or not this application provides a full WSGI stack (by
29 29 default, meaning it handles its own exceptions and errors).
30 30 Disable full_stack when this application is "managed" by
31 31 another WSGI middleware.
32 32
33 33 ``app_conf``
34 34 The application's local configuration. Normally specified in
35 35 the [app:<name>] section of the Paste ini file (where <name>
36 36 defaults to main).
37 37
38 38 """
39 39 # Configure the Pylons environment
40 40 config = load_environment(global_conf, app_conf)
41 41
42 42 # The Pylons WSGI app
43 43 app = PylonsApp(config=config)
44 44
45 45 # Routing/Session/Cache Middleware
46 46 app = RoutesMiddleware(app, config['routes.map'])
47 47 app = SessionMiddleware(app, config)
48 48
49 49 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
50 50 if asbool(config['pdebug']):
51 51 from rhodecode.lib.profiler import ProfilingMiddleware
52 52 app = ProfilingMiddleware(app)
53 53
54 if asbool(full_stack):
54 55
55 if asbool(full_stack):
56 56 # Handle Python exceptions
57 57 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
58 58
59 59 # we want our low level middleware to get to the request ASAP. We don't
60 60 # need any pylons stack middleware in them
61 61 app = SimpleHg(app, config)
62 62 app = SimpleGit(app, config)
63 63
64 64 # Display error documents for 401, 403, 404 status codes (and
65 65 # 500 when debug is disabled)
66 66 if asbool(config['debug']):
67 67 app = StatusCodeRedirect(app)
68 68 else:
69 69 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
70 70
71 71 #enable https redirets based on HTTP_X_URL_SCHEME set by proxy
72 72 app = HttpsFixup(app, config)
73 73
74 74 # Establish the Registry for this application
75 75 app = RegistryManager(app)
76 76
77 77 if asbool(static_files):
78 78 # Serve static files
79 79 static_app = StaticURLParser(config['pylons.paths']['static_files'])
80 80 app = Cascade([static_app, app])
81 81 app = make_gzip_middleware(app, global_conf, compress_level=1)
82 82
83
84 83 app.config = config
85 84
86 85 return app
@@ -1,497 +1,507 b''
1 1 """
2 2 Routes configuration
3 3
4 4 The more specific and detailed routes should be defined first so they
5 5 may take precedent over the more generic routes. For more information
6 6 refer to the routes manual at http://routes.groovie.org/docs/
7 7 """
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10
11 11 # prefix for non repository related links needs to be prefixed with `/`
12 12 ADMIN_PREFIX = '/_admin'
13 13
14 14
15 15 def make_map(config):
16 16 """Create, configure and return the routes Mapper"""
17 17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 18 always_scan=config['debug'])
19 19 rmap.minimization = False
20 20 rmap.explicit = False
21 21
22 22 from rhodecode.lib.utils import is_valid_repo
23 23 from rhodecode.lib.utils import is_valid_repos_group
24 24
25 25 def check_repo(environ, match_dict):
26 26 """
27 27 check for valid repository for proper 404 handling
28 28
29 29 :param environ:
30 30 :param match_dict:
31 31 """
32 32 from rhodecode.model.db import Repository
33 33 repo_name = match_dict.get('repo_name')
34 34
35 35 try:
36 36 by_id = repo_name.split('_')
37 37 if len(by_id) == 2 and by_id[1].isdigit():
38 38 repo_name = Repository.get(by_id[1]).repo_name
39 39 match_dict['repo_name'] = repo_name
40 40 except:
41 41 pass
42 42
43 43 return is_valid_repo(repo_name, config['base_path'])
44 44
45 45 def check_group(environ, match_dict):
46 46 """
47 47 check for valid repositories group for proper 404 handling
48 48
49 49 :param environ:
50 50 :param match_dict:
51 51 """
52 52 repos_group_name = match_dict.get('group_name')
53 53
54 54 return is_valid_repos_group(repos_group_name, config['base_path'])
55 55
56 56 def check_int(environ, match_dict):
57 57 return match_dict.get('id').isdigit()
58 58
59 59 # The ErrorController route (handles 404/500 error pages); it should
60 60 # likely stay at the top, ensuring it can always be resolved
61 61 rmap.connect('/error/{action}', controller='error')
62 62 rmap.connect('/error/{action}/{id}', controller='error')
63 63
64 64 #==========================================================================
65 65 # CUSTOM ROUTES HERE
66 66 #==========================================================================
67 67
68 68 #MAIN PAGE
69 69 rmap.connect('home', '/', controller='home', action='index')
70 70 rmap.connect('repo_switcher', '/repos', controller='home',
71 71 action='repo_switcher')
72 72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}',
73 73 controller='home', action='branch_tag_switcher')
74 74 rmap.connect('bugtracker',
75 75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
76 76 _static=True)
77 77 rmap.connect('rst_help',
78 78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
79 79 _static=True)
80 80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
81 81
82 82 #ADMIN REPOSITORY REST ROUTES
83 83 with rmap.submapper(path_prefix=ADMIN_PREFIX,
84 84 controller='admin/repos') as m:
85 85 m.connect("repos", "/repos",
86 86 action="create", conditions=dict(method=["POST"]))
87 87 m.connect("repos", "/repos",
88 88 action="index", conditions=dict(method=["GET"]))
89 89 m.connect("formatted_repos", "/repos.{format}",
90 90 action="index",
91 91 conditions=dict(method=["GET"]))
92 92 m.connect("new_repo", "/repos/new",
93 93 action="new", conditions=dict(method=["GET"]))
94 94 m.connect("formatted_new_repo", "/repos/new.{format}",
95 95 action="new", conditions=dict(method=["GET"]))
96 96 m.connect("/repos/{repo_name:.*}",
97 97 action="update", conditions=dict(method=["PUT"],
98 98 function=check_repo))
99 99 m.connect("/repos/{repo_name:.*}",
100 100 action="delete", conditions=dict(method=["DELETE"],
101 101 function=check_repo))
102 102 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
103 103 action="edit", conditions=dict(method=["GET"],
104 104 function=check_repo))
105 105 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
106 106 action="edit", conditions=dict(method=["GET"],
107 107 function=check_repo))
108 108 m.connect("repo", "/repos/{repo_name:.*}",
109 109 action="show", conditions=dict(method=["GET"],
110 110 function=check_repo))
111 111 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
112 112 action="show", conditions=dict(method=["GET"],
113 113 function=check_repo))
114 114 #ajax delete repo perm user
115 115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
116 action="delete_perm_user", conditions=dict(method=["DELETE"],
117 function=check_repo))
116 action="delete_perm_user",
117 conditions=dict(method=["DELETE"], function=check_repo))
118
118 119 #ajax delete repo perm users_group
119 120 m.connect('delete_repo_users_group',
120 121 "/repos_delete_users_group/{repo_name:.*}",
121 122 action="delete_perm_users_group",
122 123 conditions=dict(method=["DELETE"], function=check_repo))
123 124
124 125 #settings actions
125 126 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
126 127 action="repo_stats", conditions=dict(method=["DELETE"],
127 128 function=check_repo))
128 129 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
129 130 action="repo_cache", conditions=dict(method=["DELETE"],
130 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 133 action="repo_public_journal", conditions=dict(method=["PUT"],
133 134 function=check_repo))
134 135 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
135 136 action="repo_pull", conditions=dict(method=["PUT"],
136 137 function=check_repo))
137 138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*}",
138 139 action="repo_as_fork", conditions=dict(method=["PUT"],
139 140 function=check_repo))
140 141
141 142 with rmap.submapper(path_prefix=ADMIN_PREFIX,
142 143 controller='admin/repos_groups') as m:
143 144 m.connect("repos_groups", "/repos_groups",
144 145 action="create", conditions=dict(method=["POST"]))
145 146 m.connect("repos_groups", "/repos_groups",
146 147 action="index", conditions=dict(method=["GET"]))
147 148 m.connect("formatted_repos_groups", "/repos_groups.{format}",
148 149 action="index", conditions=dict(method=["GET"]))
149 150 m.connect("new_repos_group", "/repos_groups/new",
150 151 action="new", conditions=dict(method=["GET"]))
151 152 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
152 153 action="new", conditions=dict(method=["GET"]))
153 154 m.connect("update_repos_group", "/repos_groups/{id}",
154 155 action="update", conditions=dict(method=["PUT"],
155 156 function=check_int))
156 157 m.connect("delete_repos_group", "/repos_groups/{id}",
157 158 action="delete", conditions=dict(method=["DELETE"],
158 159 function=check_int))
159 160 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
160 161 action="edit", conditions=dict(method=["GET"],
161 162 function=check_int))
162 163 m.connect("formatted_edit_repos_group",
163 164 "/repos_groups/{id}.{format}/edit",
164 165 action="edit", conditions=dict(method=["GET"],
165 166 function=check_int))
166 167 m.connect("repos_group", "/repos_groups/{id}",
167 168 action="show", conditions=dict(method=["GET"],
168 169 function=check_int))
169 170 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
170 171 action="show", conditions=dict(method=["GET"],
171 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 185 #ADMIN USER REST ROUTES
174 186 with rmap.submapper(path_prefix=ADMIN_PREFIX,
175 187 controller='admin/users') as m:
176 188 m.connect("users", "/users",
177 189 action="create", conditions=dict(method=["POST"]))
178 190 m.connect("users", "/users",
179 191 action="index", conditions=dict(method=["GET"]))
180 192 m.connect("formatted_users", "/users.{format}",
181 193 action="index", conditions=dict(method=["GET"]))
182 194 m.connect("new_user", "/users/new",
183 195 action="new", conditions=dict(method=["GET"]))
184 196 m.connect("formatted_new_user", "/users/new.{format}",
185 197 action="new", conditions=dict(method=["GET"]))
186 198 m.connect("update_user", "/users/{id}",
187 199 action="update", conditions=dict(method=["PUT"]))
188 200 m.connect("delete_user", "/users/{id}",
189 201 action="delete", conditions=dict(method=["DELETE"]))
190 202 m.connect("edit_user", "/users/{id}/edit",
191 203 action="edit", conditions=dict(method=["GET"]))
192 204 m.connect("formatted_edit_user",
193 205 "/users/{id}.{format}/edit",
194 206 action="edit", conditions=dict(method=["GET"]))
195 207 m.connect("user", "/users/{id}",
196 208 action="show", conditions=dict(method=["GET"]))
197 209 m.connect("formatted_user", "/users/{id}.{format}",
198 210 action="show", conditions=dict(method=["GET"]))
199 211
200 212 #EXTRAS USER ROUTES
201 213 m.connect("user_perm", "/users_perm/{id}",
202 214 action="update_perm", conditions=dict(method=["PUT"]))
203 215
204 216 #ADMIN USERS REST ROUTES
205 217 with rmap.submapper(path_prefix=ADMIN_PREFIX,
206 218 controller='admin/users_groups') as m:
207 219 m.connect("users_groups", "/users_groups",
208 220 action="create", conditions=dict(method=["POST"]))
209 221 m.connect("users_groups", "/users_groups",
210 222 action="index", conditions=dict(method=["GET"]))
211 223 m.connect("formatted_users_groups", "/users_groups.{format}",
212 224 action="index", conditions=dict(method=["GET"]))
213 225 m.connect("new_users_group", "/users_groups/new",
214 226 action="new", conditions=dict(method=["GET"]))
215 227 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
216 228 action="new", conditions=dict(method=["GET"]))
217 229 m.connect("update_users_group", "/users_groups/{id}",
218 230 action="update", conditions=dict(method=["PUT"]))
219 231 m.connect("delete_users_group", "/users_groups/{id}",
220 232 action="delete", conditions=dict(method=["DELETE"]))
221 233 m.connect("edit_users_group", "/users_groups/{id}/edit",
222 234 action="edit", conditions=dict(method=["GET"]))
223 235 m.connect("formatted_edit_users_group",
224 236 "/users_groups/{id}.{format}/edit",
225 237 action="edit", conditions=dict(method=["GET"]))
226 238 m.connect("users_group", "/users_groups/{id}",
227 239 action="show", conditions=dict(method=["GET"]))
228 240 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
229 241 action="show", conditions=dict(method=["GET"]))
230 242
231 243 #EXTRAS USER ROUTES
232 244 m.connect("users_group_perm", "/users_groups_perm/{id}",
233 245 action="update_perm", conditions=dict(method=["PUT"]))
234 246
235 247 #ADMIN GROUP REST ROUTES
236 248 rmap.resource('group', 'groups',
237 249 controller='admin/groups', path_prefix=ADMIN_PREFIX)
238 250
239 251 #ADMIN PERMISSIONS REST ROUTES
240 252 rmap.resource('permission', 'permissions',
241 253 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
242 254
243 255 ##ADMIN LDAP SETTINGS
244 256 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
245 257 controller='admin/ldap_settings', action='ldap_settings',
246 258 conditions=dict(method=["POST"]))
247 259
248 260 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
249 261 controller='admin/ldap_settings')
250 262
251 263 #ADMIN SETTINGS REST ROUTES
252 264 with rmap.submapper(path_prefix=ADMIN_PREFIX,
253 265 controller='admin/settings') as m:
254 266 m.connect("admin_settings", "/settings",
255 267 action="create", conditions=dict(method=["POST"]))
256 268 m.connect("admin_settings", "/settings",
257 269 action="index", conditions=dict(method=["GET"]))
258 270 m.connect("formatted_admin_settings", "/settings.{format}",
259 271 action="index", conditions=dict(method=["GET"]))
260 272 m.connect("admin_new_setting", "/settings/new",
261 273 action="new", conditions=dict(method=["GET"]))
262 274 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
263 275 action="new", conditions=dict(method=["GET"]))
264 276 m.connect("/settings/{setting_id}",
265 277 action="update", conditions=dict(method=["PUT"]))
266 278 m.connect("/settings/{setting_id}",
267 279 action="delete", conditions=dict(method=["DELETE"]))
268 280 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
269 281 action="edit", conditions=dict(method=["GET"]))
270 282 m.connect("formatted_admin_edit_setting",
271 283 "/settings/{setting_id}.{format}/edit",
272 284 action="edit", conditions=dict(method=["GET"]))
273 285 m.connect("admin_setting", "/settings/{setting_id}",
274 286 action="show", conditions=dict(method=["GET"]))
275 287 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
276 288 action="show", conditions=dict(method=["GET"]))
277 289 m.connect("admin_settings_my_account", "/my_account",
278 290 action="my_account", conditions=dict(method=["GET"]))
279 291 m.connect("admin_settings_my_account_update", "/my_account_update",
280 292 action="my_account_update", conditions=dict(method=["PUT"]))
281 293 m.connect("admin_settings_create_repository", "/create_repository",
282 294 action="create_repository", conditions=dict(method=["GET"]))
283 295
284 296 #NOTIFICATION REST ROUTES
285 297 with rmap.submapper(path_prefix=ADMIN_PREFIX,
286 298 controller='admin/notifications') as m:
287 299 m.connect("notifications", "/notifications",
288 300 action="create", conditions=dict(method=["POST"]))
289 301 m.connect("notifications", "/notifications",
290 302 action="index", conditions=dict(method=["GET"]))
291 303 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
292 304 action="mark_all_read", conditions=dict(method=["GET"]))
293 305 m.connect("formatted_notifications", "/notifications.{format}",
294 306 action="index", conditions=dict(method=["GET"]))
295 307 m.connect("new_notification", "/notifications/new",
296 308 action="new", conditions=dict(method=["GET"]))
297 309 m.connect("formatted_new_notification", "/notifications/new.{format}",
298 310 action="new", conditions=dict(method=["GET"]))
299 311 m.connect("/notification/{notification_id}",
300 312 action="update", conditions=dict(method=["PUT"]))
301 313 m.connect("/notification/{notification_id}",
302 314 action="delete", conditions=dict(method=["DELETE"]))
303 315 m.connect("edit_notification", "/notification/{notification_id}/edit",
304 316 action="edit", conditions=dict(method=["GET"]))
305 317 m.connect("formatted_edit_notification",
306 318 "/notification/{notification_id}.{format}/edit",
307 319 action="edit", conditions=dict(method=["GET"]))
308 320 m.connect("notification", "/notification/{notification_id}",
309 321 action="show", conditions=dict(method=["GET"]))
310 322 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
311 323 action="show", conditions=dict(method=["GET"]))
312 324
313
314
315 325 #ADMIN MAIN PAGES
316 326 with rmap.submapper(path_prefix=ADMIN_PREFIX,
317 327 controller='admin/admin') as m:
318 328 m.connect('admin_home', '', action='index')
319 329 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
320 330 action='add_repo')
321 331
322 332 #==========================================================================
323 # API V1
333 # API V2
324 334 #==========================================================================
325 335 with rmap.submapper(path_prefix=ADMIN_PREFIX,
326 336 controller='api/api') as m:
327 337 m.connect('api', '/api')
328 338
329
330 339 #USER JOURNAL
331 340 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
332 341
333 342 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
334 343 controller='journal', action="public_journal")
335 344
336 345 rmap.connect('public_journal_rss', '%s/public_journal_rss' % ADMIN_PREFIX,
337 346 controller='journal', action="public_journal_rss")
338 347
339 348 rmap.connect('public_journal_atom',
340 349 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
341 350 action="public_journal_atom")
342 351
343 352 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
344 353 controller='journal', action='toggle_following',
345 354 conditions=dict(method=["POST"]))
346 355
347 356 #SEARCH
348 357 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
349 358 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
350 359 controller='search')
351 360
352 361 #LOGIN/LOGOUT/REGISTER/SIGN IN
353 362 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
354 363 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
355 364 action='logout')
356 365
357 366 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
358 367 action='register')
359 368
360 369 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
361 370 controller='login', action='password_reset')
362 371
363 372 rmap.connect('reset_password_confirmation',
364 373 '%s/password_reset_confirmation' % ADMIN_PREFIX,
365 374 controller='login', action='password_reset_confirmation')
366 375
367 376 #FEEDS
368 377 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
369 378 controller='feed', action='rss',
370 379 conditions=dict(function=check_repo))
371 380
372 381 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
373 382 controller='feed', action='atom',
374 383 conditions=dict(function=check_repo))
375 384
376 385 #==========================================================================
377 386 # REPOSITORY ROUTES
378 387 #==========================================================================
379 388 rmap.connect('summary_home', '/{repo_name:.*}',
380 389 controller='summary',
381 390 conditions=dict(function=check_repo))
382 391
383 392 rmap.connect('repos_group_home', '/{group_name:.*}',
384 393 controller='admin/repos_groups', action="show_by_name",
385 394 conditions=dict(function=check_group))
386 395
387 396 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
388 397 controller='changeset', revision='tip',
389 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 402 controller='changeset', revision='tip', action='comment',
393 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 407 controller='changeset', action='delete_comment',
397 408 conditions=dict(function=check_repo, method=["DELETE"]))
398 409
399 410 rmap.connect('raw_changeset_home',
400 411 '/{repo_name:.*}/raw-changeset/{revision}',
401 412 controller='changeset', action='raw_changeset',
402 413 revision='tip', conditions=dict(function=check_repo))
403 414
404 415 rmap.connect('summary_home', '/{repo_name:.*}/summary',
405 416 controller='summary', conditions=dict(function=check_repo))
406 417
407 418 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
408 419 controller='shortlog', conditions=dict(function=check_repo))
409 420
410 421 rmap.connect('branches_home', '/{repo_name:.*}/branches',
411 422 controller='branches', conditions=dict(function=check_repo))
412 423
413 424 rmap.connect('tags_home', '/{repo_name:.*}/tags',
414 425 controller='tags', conditions=dict(function=check_repo))
415 426
416 427 rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
417 428 controller='bookmarks', conditions=dict(function=check_repo))
418 429
419 430 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
420 431 controller='changelog', conditions=dict(function=check_repo))
421 432
422 433 rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
423 434 controller='changelog', action='changelog_details',
424 435 conditions=dict(function=check_repo))
425 436
426 437 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
427 438 controller='files', revision='tip', f_path='',
428 439 conditions=dict(function=check_repo))
429 440
430 441 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
431 442 controller='files', action='diff', revision='tip', f_path='',
432 443 conditions=dict(function=check_repo))
433 444
434 445 rmap.connect('files_rawfile_home',
435 446 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
436 447 controller='files', action='rawfile', revision='tip',
437 448 f_path='', conditions=dict(function=check_repo))
438 449
439 450 rmap.connect('files_raw_home',
440 451 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
441 452 controller='files', action='raw', revision='tip', f_path='',
442 453 conditions=dict(function=check_repo))
443 454
444 455 rmap.connect('files_annotate_home',
445 456 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
446 457 controller='files', action='annotate', revision='tip',
447 458 f_path='', conditions=dict(function=check_repo))
448 459
449 460 rmap.connect('files_edit_home',
450 461 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
451 462 controller='files', action='edit', revision='tip',
452 463 f_path='', conditions=dict(function=check_repo))
453 464
454 465 rmap.connect('files_add_home',
455 466 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
456 467 controller='files', action='add', revision='tip',
457 468 f_path='', conditions=dict(function=check_repo))
458 469
459 470 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
460 471 controller='files', action='archivefile',
461 472 conditions=dict(function=check_repo))
462 473
463 474 rmap.connect('files_nodelist_home',
464 475 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
465 476 controller='files', action='nodelist',
466 477 conditions=dict(function=check_repo))
467 478
468 479 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
469 480 controller='settings', action="delete",
470 481 conditions=dict(method=["DELETE"], function=check_repo))
471 482
472 483 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
473 484 controller='settings', action="update",
474 485 conditions=dict(method=["PUT"], function=check_repo))
475 486
476 487 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
477 488 controller='settings', action='index',
478 489 conditions=dict(function=check_repo))
479 490
480 491 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
481 492 controller='forks', action='fork_create',
482 493 conditions=dict(function=check_repo, method=["POST"]))
483 494
484 495 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
485 496 controller='forks', action='fork',
486 497 conditions=dict(function=check_repo))
487 498
488 499 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
489 500 controller='forks', action='forks',
490 501 conditions=dict(function=check_repo))
491 502
492 503 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
493 504 controller='followers', action='followers',
494 505 conditions=dict(function=check_repo))
495 506
496
497 507 return rmap
@@ -1,431 +1,433 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 Admin controller for RhodeCode
6 Repositories controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from paste.httpexceptions import HTTPInternalServerError
32 32 from pylons import request, session, tmpl_context as c, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.exc import IntegrityError
36 36
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 39 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 42 from rhodecode.lib.helpers import get_token
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
45 45 from rhodecode.model.forms import RepoForm
46 46 from rhodecode.model.scm import ScmModel
47 47 from rhodecode.model.repo import RepoModel
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class ReposController(BaseController):
53 53 """
54 54 REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('repo', 'repos')
58 58
59 59 @LoginRequired()
60 60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 61 def __before__(self):
62 62 c.admin_user = session.get('admin_user')
63 63 c.admin_username = session.get('admin_username')
64 64 super(ReposController, self).__before__()
65 65
66 66 def __load_defaults(self):
67 67 c.repo_groups = RepoGroup.groups_choices()
68 68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
69 69
70 70 repo_model = RepoModel()
71 71 c.users_array = repo_model.get_users_js()
72 72 c.users_groups_array = repo_model.get_users_groups_js()
73 73
74 74 def __load_data(self, repo_name=None):
75 75 """
76 76 Load defaults settings for edit, and update
77 77
78 78 :param repo_name:
79 79 """
80 80 self.__load_defaults()
81 81
82 82 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
83 83 repo = db_repo.scm_instance
84 84
85 85 if c.repo_info is None:
86 86 h.flash(_('%s repository is not mapped to db perhaps'
87 87 ' it was created or renamed from the filesystem'
88 88 ' please run the application again'
89 89 ' in order to rescan repositories') % repo_name,
90 90 category='error')
91 91
92 92 return redirect(url('repos'))
93 93
94 94 c.default_user_id = User.get_by_username('default').user_id
95 95 c.in_public_journal = UserFollowing.query()\
96 96 .filter(UserFollowing.user_id == c.default_user_id)\
97 97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
98 98
99 99 if c.repo_info.stats:
100 100 # this is on what revision we ended up so we add +1 for count
101 101 last_rev = c.repo_info.stats.stat_on_revision + 1
102 102 else:
103 103 last_rev = 0
104 104 c.stats_revision = last_rev
105 105
106 106 c.repo_last_rev = repo.count() if repo.revisions else 0
107 107
108 108 if last_rev == 0 or c.repo_last_rev == 0:
109 109 c.stats_percentage = 0
110 110 else:
111 111 c.stats_percentage = '%.2f' % ((float((last_rev)) /
112 112 c.repo_last_rev) * 100)
113 113
114 114 defaults = RepoModel()._get_defaults(repo_name)
115 115
116 116 c.repos_list = [('', _('--REMOVE FORK--'))]
117 117 c.repos_list += [(x.repo_id, x.repo_name) for x in
118 118 Repository.query().order_by(Repository.repo_name).all()]
119 119 return defaults
120 120
121 121 @HasPermissionAllDecorator('hg.admin')
122 122 def index(self, format='html'):
123 123 """GET /repos: All items in the collection"""
124 124 # url('repos')
125 125
126 126 c.repos_list = ScmModel().get_repos(Repository.query()
127 127 .order_by(Repository.repo_name)
128 128 .all(), sort_key='name_sort')
129 129 return render('admin/repos/repos.html')
130 130
131 131 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
132 132 def create(self):
133 133 """
134 134 POST /repos: Create a new item"""
135 135 # url('repos')
136 136
137 137 self.__load_defaults()
138 138 form_result = {}
139 139 try:
140 140 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
141 141 .to_python(dict(request.POST))
142 142 RepoModel().create(form_result, self.rhodecode_user)
143 143 if form_result['clone_uri']:
144 144 h.flash(_('created repository %s from %s') \
145 145 % (form_result['repo_name'], form_result['clone_uri']),
146 146 category='success')
147 147 else:
148 148 h.flash(_('created repository %s') % form_result['repo_name'],
149 149 category='success')
150 150
151 151 if request.POST.get('user_created'):
152 152 # created by regular non admin user
153 153 action_logger(self.rhodecode_user, 'user_created_repo',
154 154 form_result['repo_name_full'], '', self.sa)
155 155 else:
156 156 action_logger(self.rhodecode_user, 'admin_created_repo',
157 157 form_result['repo_name_full'], '', self.sa)
158 158 Session.commit()
159 159 except formencode.Invalid, errors:
160 160
161 161 c.new_repo = errors.value['repo_name']
162 162
163 163 if request.POST.get('user_created'):
164 164 r = render('admin/repos/repo_add_create_repository.html')
165 165 else:
166 166 r = render('admin/repos/repo_add.html')
167 167
168 168 return htmlfill.render(
169 169 r,
170 170 defaults=errors.value,
171 171 errors=errors.error_dict or {},
172 172 prefix_error=False,
173 173 encoding="UTF-8")
174 174
175 175 except Exception:
176 176 log.error(traceback.format_exc())
177 177 msg = _('error occurred during creation of repository %s') \
178 178 % form_result.get('repo_name')
179 179 h.flash(msg, category='error')
180 180 if request.POST.get('user_created'):
181 181 return redirect(url('home'))
182 182 return redirect(url('repos'))
183 183
184 184 @HasPermissionAllDecorator('hg.admin')
185 185 def new(self, format='html'):
186 186 """GET /repos/new: Form to create a new item"""
187 187 new_repo = request.GET.get('repo', '')
188 188 c.new_repo = repo_name_slug(new_repo)
189 189 self.__load_defaults()
190 190 return render('admin/repos/repo_add.html')
191 191
192 192 @HasPermissionAllDecorator('hg.admin')
193 193 def update(self, repo_name):
194 194 """
195 195 PUT /repos/repo_name: Update an existing item"""
196 196 # Forms posted to this method should contain a hidden field:
197 197 # <input type="hidden" name="_method" value="PUT" />
198 198 # Or using helpers:
199 199 # h.form(url('repo', repo_name=ID),
200 200 # method='put')
201 201 # url('repo', repo_name=ID)
202 202 self.__load_defaults()
203 203 repo_model = RepoModel()
204 204 changed_name = repo_name
205 205 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
206 206 repo_groups=c.repo_groups_choices)()
207 207 try:
208 208 form_result = _form.to_python(dict(request.POST))
209 209 repo = repo_model.update(repo_name, form_result)
210 210 invalidate_cache('get_repo_cached_%s' % repo_name)
211 211 h.flash(_('Repository %s updated successfully' % repo_name),
212 212 category='success')
213 213 changed_name = repo.repo_name
214 214 action_logger(self.rhodecode_user, 'admin_updated_repo',
215 215 changed_name, '', self.sa)
216 216 Session.commit()
217 217 except formencode.Invalid, errors:
218 218 defaults = self.__load_data(repo_name)
219 219 defaults.update(errors.value)
220 220 return htmlfill.render(
221 221 render('admin/repos/repo_edit.html'),
222 222 defaults=defaults,
223 223 errors=errors.error_dict or {},
224 224 prefix_error=False,
225 225 encoding="UTF-8")
226 226
227 227 except Exception:
228 228 log.error(traceback.format_exc())
229 229 h.flash(_('error occurred during update of repository %s') \
230 230 % repo_name, category='error')
231 231 return redirect(url('edit_repo', repo_name=changed_name))
232 232
233 233 @HasPermissionAllDecorator('hg.admin')
234 234 def delete(self, repo_name):
235 235 """
236 236 DELETE /repos/repo_name: Delete an existing item"""
237 237 # Forms posted to this method should contain a hidden field:
238 238 # <input type="hidden" name="_method" value="DELETE" />
239 239 # Or using helpers:
240 240 # h.form(url('repo', repo_name=ID),
241 241 # method='delete')
242 242 # url('repo', repo_name=ID)
243 243
244 244 repo_model = RepoModel()
245 245 repo = repo_model.get_by_repo_name(repo_name)
246 246 if not repo:
247 247 h.flash(_('%s repository is not mapped to db perhaps'
248 248 ' it was moved or renamed from the filesystem'
249 249 ' please run the application again'
250 250 ' in order to rescan repositories') % repo_name,
251 251 category='error')
252 252
253 253 return redirect(url('repos'))
254 254 try:
255 255 action_logger(self.rhodecode_user, 'admin_deleted_repo',
256 256 repo_name, '', self.sa)
257 257 repo_model.delete(repo)
258 258 invalidate_cache('get_repo_cached_%s' % repo_name)
259 259 h.flash(_('deleted repository %s') % repo_name, category='success')
260 260 Session.commit()
261 261 except IntegrityError, e:
262 262 if e.message.find('repositories_fork_id_fkey') != -1:
263 263 log.error(traceback.format_exc())
264 264 h.flash(_('Cannot delete %s it still contains attached '
265 265 'forks') % repo_name,
266 266 category='warning')
267 267 else:
268 268 log.error(traceback.format_exc())
269 269 h.flash(_('An error occurred during '
270 270 'deletion of %s') % repo_name,
271 271 category='error')
272 272
273 273 except Exception, e:
274 274 log.error(traceback.format_exc())
275 275 h.flash(_('An error occurred during deletion of %s') % repo_name,
276 276 category='error')
277 277
278 278 return redirect(url('repos'))
279 279
280
281 280 @HasRepoPermissionAllDecorator('repository.admin')
282 281 def delete_perm_user(self, repo_name):
283 282 """
284 283 DELETE an existing repository permission user
285 284
286 285 :param repo_name:
287 286 """
288 287
289 288 try:
290 repo_model = RepoModel()
291 repo_model.delete_perm_user(request.POST, repo_name)
289 RepoModel().revoke_user_permission(repo=repo_name,
290 user=request.POST['user_id'])
292 291 Session.commit()
293 except Exception, e:
292 except Exception:
293 log.error(traceback.format_exc())
294 294 h.flash(_('An error occurred during deletion of repository user'),
295 295 category='error')
296 296 raise HTTPInternalServerError()
297 297
298 298 @HasRepoPermissionAllDecorator('repository.admin')
299 299 def delete_perm_users_group(self, repo_name):
300 300 """
301 301 DELETE an existing repository permission users group
302 302
303 303 :param repo_name:
304 304 """
305
305 306 try:
306 repo_model = RepoModel()
307 repo_model.delete_perm_users_group(request.POST, repo_name)
307 RepoModel().revoke_users_group_permission(
308 repo=repo_name, group_name=request.POST['users_group_id']
309 )
308 310 Session.commit()
309 except Exception, e:
311 except Exception:
312 log.error(traceback.format_exc())
310 313 h.flash(_('An error occurred during deletion of repository'
311 314 ' users groups'),
312 315 category='error')
313 316 raise HTTPInternalServerError()
314 317
315 318 @HasPermissionAllDecorator('hg.admin')
316 319 def repo_stats(self, repo_name):
317 320 """
318 321 DELETE an existing repository statistics
319 322
320 323 :param repo_name:
321 324 """
322 325
323 326 try:
324 repo_model = RepoModel()
325 repo_model.delete_stats(repo_name)
327 RepoModel().delete_stats(repo_name)
326 328 Session.commit()
327 329 except Exception, e:
328 330 h.flash(_('An error occurred during deletion of repository stats'),
329 331 category='error')
330 332 return redirect(url('edit_repo', repo_name=repo_name))
331 333
332 334 @HasPermissionAllDecorator('hg.admin')
333 335 def repo_cache(self, repo_name):
334 336 """
335 337 INVALIDATE existing repository cache
336 338
337 339 :param repo_name:
338 340 """
339 341
340 342 try:
341 343 ScmModel().mark_for_invalidation(repo_name)
342 344 Session.commit()
343 345 except Exception, e:
344 346 h.flash(_('An error occurred during cache invalidation'),
345 347 category='error')
346 348 return redirect(url('edit_repo', repo_name=repo_name))
347 349
348 350 @HasPermissionAllDecorator('hg.admin')
349 351 def repo_public_journal(self, repo_name):
350 352 """
351 353 Set's this repository to be visible in public journal,
352 354 in other words assing default user to follow this repo
353 355
354 356 :param repo_name:
355 357 """
356 358
357 359 cur_token = request.POST.get('auth_token')
358 360 token = get_token()
359 361 if cur_token == token:
360 362 try:
361 363 repo_id = Repository.get_by_repo_name(repo_name).repo_id
362 364 user_id = User.get_by_username('default').user_id
363 365 self.scm_model.toggle_following_repo(repo_id, user_id)
364 366 h.flash(_('Updated repository visibility in public journal'),
365 367 category='success')
366 368 Session.commit()
367 369 except:
368 370 h.flash(_('An error occurred during setting this'
369 371 ' repository in public journal'),
370 372 category='error')
371 373
372 374 else:
373 375 h.flash(_('Token mismatch'), category='error')
374 376 return redirect(url('edit_repo', repo_name=repo_name))
375 377
376 378 @HasPermissionAllDecorator('hg.admin')
377 379 def repo_pull(self, repo_name):
378 380 """
379 381 Runs task to update given repository with remote changes,
380 382 ie. make pull on remote location
381 383
382 384 :param repo_name:
383 385 """
384 386 try:
385 387 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
386 388 h.flash(_('Pulled from remote location'), category='success')
387 389 except Exception, e:
388 390 h.flash(_('An error occurred during pull from remote location'),
389 391 category='error')
390 392
391 393 return redirect(url('edit_repo', repo_name=repo_name))
392 394
393 395 @HasPermissionAllDecorator('hg.admin')
394 396 def repo_as_fork(self, repo_name):
395 397 """
396 398 Mark given repository as a fork of another
397 399
398 400 :param repo_name:
399 401 """
400 402 try:
401 403 fork_id = request.POST.get('id_fork_of')
402 404 repo = ScmModel().mark_as_fork(repo_name, fork_id,
403 405 self.rhodecode_user.username)
404 406 fork = repo.fork.repo_name if repo.fork else _('Nothing')
405 407 Session.commit()
406 408 h.flash(_('Marked repo %s as fork of %s' % (repo_name,fork)),
407 409 category='success')
408 410 except Exception, e:
409 411 raise
410 412 h.flash(_('An error occurred during this operation'),
411 413 category='error')
412 414
413 415 return redirect(url('edit_repo', repo_name=repo_name))
414 416
415 417 @HasPermissionAllDecorator('hg.admin')
416 418 def show(self, repo_name, format='html'):
417 419 """GET /repos/repo_name: Show a specific item"""
418 420 # url('repo', repo_name=ID)
419 421
420 422 @HasPermissionAllDecorator('hg.admin')
421 423 def edit(self, repo_name, format='html'):
422 424 """GET /repos/repo_name/edit: Form to edit an existing item"""
423 425 # url('edit_repo', repo_name=ID)
424 426 defaults = self.__load_data(repo_name)
425 427
426 428 return htmlfill.render(
427 429 render('admin/repos/repo_edit.html'),
428 430 defaults=defaults,
429 431 encoding="UTF-8",
430 432 force_defaults=False
431 433 )
@@ -1,250 +1,313 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 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 8 :created_on: Mar 23, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31
32 from pylons import request, response, session, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
32 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from sqlalchemy.exc import IntegrityError
37 37
38 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 41 from rhodecode.lib.base import BaseController, render
41 42 from rhodecode.model.db import RepoGroup
42 43 from rhodecode.model.repos_group import ReposGroupModel
43 44 from rhodecode.model.forms import ReposGroupForm
44 45 from rhodecode.model.meta import Session
46 from rhodecode.model.repo import RepoModel
47 from webob.exc import HTTPInternalServerError
45 48
46 49 log = logging.getLogger(__name__)
47 50
48 51
49 52 class ReposGroupsController(BaseController):
50 53 """REST Controller styled on the Atom Publishing Protocol"""
51 54 # To properly map this controller, ensure your config/routing.py
52 55 # file has a resource setup:
53 56 # map.resource('repos_group', 'repos_groups')
54 57
55 58 @LoginRequired()
56 59 def __before__(self):
57 60 super(ReposGroupsController, self).__before__()
58 61
59 62 def __load_defaults(self):
60 63 c.repo_groups = RepoGroup.groups_choices()
61 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 70 def __load_data(self, group_id):
64 71 """
65 72 Load defaults settings for edit, and update
66 73
67 74 :param group_id:
68 75 """
69 76 self.__load_defaults()
70 77
71 78 repo_group = RepoGroup.get(group_id)
72 79
73 80 data = repo_group.get_dict()
74 81
75 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 94 return data
78 95
79 96 @HasPermissionAnyDecorator('hg.admin')
80 97 def index(self, format='html'):
81 98 """GET /repos_groups: All items in the collection"""
82 99 # url('repos_groups')
83
84 100 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
85 101 c.groups = sorted(RepoGroup.query().all(), key=sk)
86 102 return render('admin/repos_groups/repos_groups_show.html')
87 103
88 104 @HasPermissionAnyDecorator('hg.admin')
89 105 def create(self):
90 106 """POST /repos_groups: Create a new item"""
91 107 # url('repos_groups')
92 108 self.__load_defaults()
93 109 repos_group_form = ReposGroupForm(available_groups =
94 110 c.repo_groups_choices)()
95 111 try:
96 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 118 Session.commit()
99 119 h.flash(_('created repos group %s') \
100 120 % form_result['group_name'], category='success')
101 121 #TODO: in futureaction_logger(, '', '', '', self.sa)
102 122 except formencode.Invalid, errors:
103 123
104 124 return htmlfill.render(
105 125 render('admin/repos_groups/repos_groups_add.html'),
106 126 defaults=errors.value,
107 127 errors=errors.error_dict or {},
108 128 prefix_error=False,
109 129 encoding="UTF-8")
110 130 except Exception:
111 131 log.error(traceback.format_exc())
112 132 h.flash(_('error occurred during creation of repos group %s') \
113 133 % request.POST.get('group_name'), category='error')
114 134
115 135 return redirect(url('repos_groups'))
116 136
117 137 @HasPermissionAnyDecorator('hg.admin')
118 138 def new(self, format='html'):
119 139 """GET /repos_groups/new: Form to create a new item"""
120 140 # url('new_repos_group')
121 141 self.__load_defaults()
122 142 return render('admin/repos_groups/repos_groups_add.html')
123 143
124 144 @HasPermissionAnyDecorator('hg.admin')
125 145 def update(self, id):
126 146 """PUT /repos_groups/id: Update an existing item"""
127 147 # Forms posted to this method should contain a hidden field:
128 148 # <input type="hidden" name="_method" value="PUT" />
129 149 # Or using helpers:
130 150 # h.form(url('repos_group', id=ID),
131 151 # method='put')
132 152 # url('repos_group', id=ID)
133 153
134 154 self.__load_defaults()
135 155 c.repos_group = RepoGroup.get(id)
136 156
137 repos_group_form = ReposGroupForm(edit=True,
138 old_data=c.repos_group.get_dict(),
139 available_groups=
140 c.repo_groups_choices)()
157 repos_group_form = ReposGroupForm(
158 edit=True,
159 old_data=c.repos_group.get_dict(),
160 available_groups=c.repo_groups_choices
161 )()
141 162 try:
142 163 form_result = repos_group_form.to_python(dict(request.POST))
143 164 ReposGroupModel().update(id, form_result)
144 165 Session.commit()
145 166 h.flash(_('updated repos group %s') \
146 167 % form_result['group_name'], category='success')
147 168 #TODO: in futureaction_logger(, '', '', '', self.sa)
148 169 except formencode.Invalid, errors:
149 170
150 171 return htmlfill.render(
151 172 render('admin/repos_groups/repos_groups_edit.html'),
152 173 defaults=errors.value,
153 174 errors=errors.error_dict or {},
154 175 prefix_error=False,
155 176 encoding="UTF-8")
156 177 except Exception:
157 178 log.error(traceback.format_exc())
158 179 h.flash(_('error occurred during update of repos group %s') \
159 180 % request.POST.get('group_name'), category='error')
160 181
161 182 return redirect(url('repos_groups'))
162 183
163 184 @HasPermissionAnyDecorator('hg.admin')
164 185 def delete(self, id):
165 186 """DELETE /repos_groups/id: Delete an existing item"""
166 187 # Forms posted to this method should contain a hidden field:
167 188 # <input type="hidden" name="_method" value="DELETE" />
168 189 # Or using helpers:
169 190 # h.form(url('repos_group', id=ID),
170 191 # method='delete')
171 192 # url('repos_group', id=ID)
172 193
173 194 gr = RepoGroup.get(id)
174 195 repos = gr.repositories.all()
175 196 if repos:
176 197 h.flash(_('This group contains %s repositores and cannot be '
177 198 'deleted' % len(repos)),
178 199 category='error')
179 200 return redirect(url('repos_groups'))
180 201
181 202 try:
182 203 ReposGroupModel().delete(id)
183 204 Session.commit()
184 205 h.flash(_('removed repos group %s' % gr.group_name), category='success')
185 206 #TODO: in future action_logger(, '', '', '', self.sa)
186 207 except IntegrityError, e:
187 208 if e.message.find('groups_group_parent_id_fkey') != -1:
188 209 log.error(traceback.format_exc())
189 210 h.flash(_('Cannot delete this group it still contains '
190 211 'subgroups'),
191 212 category='warning')
192 213 else:
193 214 log.error(traceback.format_exc())
194 215 h.flash(_('error occurred during deletion of repos '
195 216 'group %s' % gr.group_name), category='error')
196 217
197 218 except Exception:
198 219 log.error(traceback.format_exc())
199 220 h.flash(_('error occurred during deletion of repos '
200 221 'group %s' % gr.group_name), category='error')
201 222
202 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 265 def show_by_name(self, group_name):
205 266 id_ = RepoGroup.get_by_group_name(group_name).group_id
206 267 return self.show(id_)
207 268
269 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
270 'group.admin')
208 271 def show(self, id, format='html'):
209 272 """GET /repos_groups/id: Show a specific item"""
210 273 # url('repos_group', id=ID)
211 274
212 275 c.group = RepoGroup.get(id)
213 276
214 277 if c.group:
215 278 c.group_repos = c.group.repositories.all()
216 279 else:
217 280 return redirect(url('home'))
218 281
219 282 #overwrite our cached list with current filter
220 283 gr_filter = c.group_repos
221 284 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
222 285
223 286 c.repos_list = c.cached_repo_list
224 287
225 288 c.repo_cnt = 0
226 289
227 290 c.groups = self.sa.query(RepoGroup).order_by(RepoGroup.group_name)\
228 291 .filter(RepoGroup.group_parent_id == id).all()
229 292
230 293 return render('admin/repos_groups/repos_groups.html')
231 294
232 295 @HasPermissionAnyDecorator('hg.admin')
233 296 def edit(self, id, format='html'):
234 297 """GET /repos_groups/id/edit: Form to edit an existing item"""
235 298 # url('edit_repos_group', id=ID)
236 299
237 300 id_ = int(id)
238 301
239 302 c.repos_group = RepoGroup.get(id_)
240 303 defaults = self.__load_data(id_)
241 304
242 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 308 return htmlfill.render(
246 309 render('admin/repos_groups/repos_groups_edit.html'),
247 310 defaults=defaults,
248 311 encoding="UTF-8",
249 312 force_defaults=False
250 313 )
@@ -1,510 +1,571 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.api
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 API controller for RhodeCode
7 7
8 8 :created_on: Aug 20, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import traceback
29 29 import logging
30 30
31 31 from sqlalchemy.orm.exc import NoResultFound
32 32
33 33 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
34 34 from rhodecode.lib.auth import HasPermissionAllDecorator, \
35 35 HasPermissionAnyDecorator
36 36
37 37 from rhodecode.model.meta import Session
38 38 from rhodecode.model.scm import ScmModel
39 39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
40 40 from rhodecode.model.repo import RepoModel
41 41 from rhodecode.model.user import UserModel
42 42 from rhodecode.model.repo_permission import RepositoryPermissionModel
43 43 from rhodecode.model.users_group import UsersGroupModel
44 44 from rhodecode.model.repos_group import ReposGroupModel
45 45
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class ApiController(JSONRPCController):
51 51 """
52 52 API Controller
53 53
54 54
55 55 Each method needs to have USER as argument this is then based on given
56 56 API_KEY propagated as instance of user object
57 57
58 58 Preferably this should be first argument also
59 59
60 60
61 61 Each function should also **raise** JSONRPCError for any
62 62 errors that happens
63 63
64 64 """
65 65
66 66 @HasPermissionAllDecorator('hg.admin')
67 67 def pull(self, apiuser, repo_name):
68 68 """
69 69 Dispatch pull action on given repo
70 70
71 71
72 72 :param user:
73 73 :param repo_name:
74 74 """
75 75
76 76 if Repository.is_valid(repo_name) is False:
77 77 raise JSONRPCError('Unknown repo "%s"' % repo_name)
78 78
79 79 try:
80 80 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
81 81 return 'Pulled from %s' % repo_name
82 82 except Exception:
83 83 raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
84 84
85 85 @HasPermissionAllDecorator('hg.admin')
86 86 def get_user(self, apiuser, username):
87 87 """"
88 88 Get a user by username
89 89
90 90 :param apiuser:
91 91 :param username:
92 92 """
93 93
94 94 user = User.get_by_username(username)
95 95 if not user:
96 96 return None
97 97
98 98 return dict(
99 99 id=user.user_id,
100 100 username=user.username,
101 101 firstname=user.name,
102 102 lastname=user.lastname,
103 103 email=user.email,
104 104 active=user.active,
105 105 admin=user.admin,
106 106 ldap=user.ldap_dn
107 107 )
108 108
109 109 @HasPermissionAllDecorator('hg.admin')
110 110 def get_users(self, apiuser):
111 111 """"
112 112 Get all users
113 113
114 114 :param apiuser:
115 115 """
116 116
117 117 result = []
118 118 for user in User.getAll():
119 119 result.append(
120 120 dict(
121 121 id=user.user_id,
122 122 username=user.username,
123 123 firstname=user.name,
124 124 lastname=user.lastname,
125 125 email=user.email,
126 126 active=user.active,
127 127 admin=user.admin,
128 128 ldap=user.ldap_dn
129 129 )
130 130 )
131 131 return result
132 132
133 133 @HasPermissionAllDecorator('hg.admin')
134 134 def create_user(self, apiuser, username, password, email, firstname=None,
135 135 lastname=None, active=True, admin=False, ldap_dn=None):
136 136 """
137 137 Create new user or updates current one
138 138
139 139 :param apiuser:
140 140 :param username:
141 141 :param password:
142 142 :param email:
143 143 :param name:
144 144 :param lastname:
145 145 :param active:
146 146 :param admin:
147 147 :param ldap_dn:
148 148 """
149 149
150 150 if User.get_by_username(username):
151 151 raise JSONRPCError("user %s already exist" % username)
152 152
153 153 try:
154 154 usr = UserModel().create_or_update(
155 155 username, password, email, firstname,
156 156 lastname, active, admin, ldap_dn
157 157 )
158 158 Session.commit()
159 159 return dict(
160 160 id=usr.user_id,
161 161 msg='created new user %s' % username
162 162 )
163 163 except Exception:
164 164 log.error(traceback.format_exc())
165 165 raise JSONRPCError('failed to create user %s' % username)
166 166
167 167 @HasPermissionAllDecorator('hg.admin')
168 168 def get_users_group(self, apiuser, group_name):
169 169 """"
170 170 Get users group by name
171 171
172 172 :param apiuser:
173 173 :param group_name:
174 174 """
175 175
176 176 users_group = UsersGroup.get_by_group_name(group_name)
177 177 if not users_group:
178 178 return None
179 179
180 180 members = []
181 181 for user in users_group.members:
182 182 user = user.user
183 183 members.append(dict(id=user.user_id,
184 184 username=user.username,
185 185 firstname=user.name,
186 186 lastname=user.lastname,
187 187 email=user.email,
188 188 active=user.active,
189 189 admin=user.admin,
190 190 ldap=user.ldap_dn))
191 191
192 192 return dict(id=users_group.users_group_id,
193 193 group_name=users_group.users_group_name,
194 194 active=users_group.users_group_active,
195 195 members=members)
196 196
197 197 @HasPermissionAllDecorator('hg.admin')
198 198 def get_users_groups(self, apiuser):
199 199 """"
200 200 Get all users groups
201 201
202 202 :param apiuser:
203 203 """
204 204
205 205 result = []
206 206 for users_group in UsersGroup.getAll():
207 207 members = []
208 208 for user in users_group.members:
209 209 user = user.user
210 210 members.append(dict(id=user.user_id,
211 211 username=user.username,
212 212 firstname=user.name,
213 213 lastname=user.lastname,
214 214 email=user.email,
215 215 active=user.active,
216 216 admin=user.admin,
217 217 ldap=user.ldap_dn))
218 218
219 219 result.append(dict(id=users_group.users_group_id,
220 220 group_name=users_group.users_group_name,
221 221 active=users_group.users_group_active,
222 222 members=members))
223 223 return result
224 224
225 225 @HasPermissionAllDecorator('hg.admin')
226 226 def create_users_group(self, apiuser, group_name, active=True):
227 227 """
228 228 Creates an new usergroup
229 229
230 230 :param group_name:
231 231 :param active:
232 232 """
233 233
234 234 if self.get_users_group(apiuser, group_name):
235 235 raise JSONRPCError("users group %s already exist" % group_name)
236 236
237 237 try:
238 238 ug = UsersGroupModel().create(name=group_name, active=active)
239 239 Session.commit()
240 240 return dict(id=ug.users_group_id,
241 241 msg='created new users group %s' % group_name)
242 242 except Exception:
243 243 log.error(traceback.format_exc())
244 244 raise JSONRPCError('failed to create group %s' % group_name)
245 245
246 246 @HasPermissionAllDecorator('hg.admin')
247 247 def add_user_to_users_group(self, apiuser, group_name, username):
248 248 """"
249 249 Add a user to a group
250 250
251 251 :param apiuser:
252 252 :param group_name:
253 253 :param username:
254 254 """
255 255
256 256 try:
257 257 users_group = UsersGroup.get_by_group_name(group_name)
258 258 if not users_group:
259 259 raise JSONRPCError('unknown users group %s' % group_name)
260 260
261 261 try:
262 262 user = User.get_by_username(username)
263 263 except NoResultFound:
264 264 raise JSONRPCError('unknown user %s' % username)
265 265
266 266 ugm = UsersGroupModel().add_user_to_group(users_group, user)
267 267 Session.commit()
268 268 return dict(id=ugm.users_group_member_id,
269 269 msg='created new users group member')
270 270 except Exception:
271 271 log.error(traceback.format_exc())
272 272 raise JSONRPCError('failed to create users group member')
273 273
274 274 @HasPermissionAnyDecorator('hg.admin')
275 275 def get_repo(self, apiuser, repo_name):
276 276 """"
277 277 Get repository by name
278 278
279 279 :param apiuser:
280 280 :param repo_name:
281 281 """
282 282
283 283 repo = Repository.get_by_repo_name(repo_name)
284 284 if repo is None:
285 285 raise JSONRPCError('unknown repository %s' % repo)
286 286
287 287 members = []
288 288 for user in repo.repo_to_perm:
289 289 perm = user.permission.permission_name
290 290 user = user.user
291 291 members.append(
292 292 dict(
293 293 type_="user",
294 294 id=user.user_id,
295 295 username=user.username,
296 296 firstname=user.name,
297 297 lastname=user.lastname,
298 298 email=user.email,
299 299 active=user.active,
300 300 admin=user.admin,
301 301 ldap=user.ldap_dn,
302 302 permission=perm
303 303 )
304 304 )
305 305 for users_group in repo.users_group_to_perm:
306 306 perm = users_group.permission.permission_name
307 307 users_group = users_group.users_group
308 308 members.append(
309 309 dict(
310 310 type_="users_group",
311 311 id=users_group.users_group_id,
312 312 name=users_group.users_group_name,
313 313 active=users_group.users_group_active,
314 314 permission=perm
315 315 )
316 316 )
317 317
318 318 return dict(
319 319 id=repo.repo_id,
320 320 repo_name=repo.repo_name,
321 321 type=repo.repo_type,
322 322 description=repo.description,
323 323 members=members
324 324 )
325 325
326 326 @HasPermissionAnyDecorator('hg.admin')
327 327 def get_repos(self, apiuser):
328 328 """"
329 329 Get all repositories
330 330
331 331 :param apiuser:
332 332 """
333 333
334 334 result = []
335 335 for repository in Repository.getAll():
336 336 result.append(
337 337 dict(
338 338 id=repository.repo_id,
339 339 repo_name=repository.repo_name,
340 340 type=repository.repo_type,
341 341 description=repository.description
342 342 )
343 343 )
344 344 return result
345 345
346 346 @HasPermissionAnyDecorator('hg.admin')
347 347 def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
348 348 ret_type='all'):
349 349 """
350 350 returns a list of nodes and it's children
351 351 for a given path at given revision. It's possible to specify ret_type
352 352 to show only files or dirs
353 353
354 354 :param apiuser:
355 355 :param repo_name: name of repository
356 356 :param revision: revision for which listing should be done
357 357 :param root_path: path from which start displaying
358 358 :param ret_type: return type 'all|files|dirs' nodes
359 359 """
360 360 try:
361 361 _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
362 362 flat=False)
363 363 _map = {
364 364 'all': _d + _f,
365 365 'files': _f,
366 366 'dirs': _d,
367 367 }
368 368 return _map[ret_type]
369 369 except KeyError:
370 370 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
371 371 except Exception, e:
372 372 raise JSONRPCError(e)
373 373
374 374 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
375 375 def create_repo(self, apiuser, repo_name, owner_name, description='',
376 376 repo_type='hg', private=False):
377 377 """
378 378 Create a repository
379 379
380 380 :param apiuser:
381 381 :param repo_name:
382 382 :param description:
383 383 :param type:
384 384 :param private:
385 385 :param owner_name:
386 386 """
387 387
388 388 try:
389 389 try:
390 390 owner = User.get_by_username(owner_name)
391 391 except NoResultFound:
392 392 raise JSONRPCError('unknown user %s' % owner)
393 393
394 394 if Repository.get_by_repo_name(repo_name):
395 395 raise JSONRPCError("repo %s already exist" % repo_name)
396 396
397 397 groups = repo_name.split('/')
398 398 real_name = groups[-1]
399 399 groups = groups[:-1]
400 400 parent_id = None
401 401 for g in groups:
402 402 group = RepoGroup.get_by_group_name(g)
403 403 if not group:
404 group = ReposGroupModel().create(
405 dict(
406 group_name=g,
407 group_description='',
408 group_parent_id=parent_id
409 )
410 )
404 group = ReposGroupModel().create(g, '', parent_id)
411 405 parent_id = group.group_id
412 406
413 407 repo = RepoModel().create(
414 408 dict(
415 409 repo_name=real_name,
416 410 repo_name_full=repo_name,
417 411 description=description,
418 412 private=private,
419 413 repo_type=repo_type,
420 414 repo_group=parent_id,
421 415 clone_uri=None
422 416 ),
423 417 owner
424 418 )
425 419 Session.commit()
426 420
427 421 return dict(
428 422 id=repo.repo_id,
429 423 msg="Created new repository %s" % repo.repo_name
430 424 )
431 425
432 426 except Exception:
433 427 log.error(traceback.format_exc())
434 428 raise JSONRPCError('failed to create repository %s' % repo_name)
435 429
436 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 436 :param repo_name:
443 437 :param username:
444 438 :param perm:
445 439 """
446 440
447 441 try:
448 442 repo = Repository.get_by_repo_name(repo_name)
449 443 if repo is None:
450 444 raise JSONRPCError('unknown repository %s' % repo)
451 445
452 try:
453 user = User.get_by_username(username)
454 except NoResultFound:
455 raise JSONRPCError('unknown user %s' % user)
446 user = User.get_by_username(username)
447 if user is None:
448 raise JSONRPCError('unknown user %s' % username)
456 449
457 RepositoryPermissionModel()\
458 .update_or_delete_user_permission(repo, user, perm)
450 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
451
459 452 Session.commit()
460
461 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 455 perm, username, repo_name
464 456 )
465 457 )
466 458 except Exception:
467 459 log.error(traceback.format_exc())
468 460 raise JSONRPCError(
469 461 'failed to edit permission %(repo)s for %(user)s' % dict(
470 462 user=username, repo=repo_name
471 463 )
472 464 )
473 465
474 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 506 :param repo_name:
481 507 :param group_name:
482 508 :param perm:
483 509 """
484 510
485 511 try:
486 512 repo = Repository.get_by_repo_name(repo_name)
487 513 if repo is None:
488 514 raise JSONRPCError('unknown repository %s' % repo)
489 515
490 try:
491 user_group = UsersGroup.get_by_group_name(group_name)
492 except NoResultFound:
516 user_group = UsersGroup.get_by_group_name(group_name)
517 if user_group is None:
493 518 raise JSONRPCError('unknown users group %s' % user_group)
494 519
495 RepositoryPermissionModel()\
496 .update_or_delete_users_group_permission(repo, user_group,
497 perm)
520 RepoModel().grant_users_group_permission(repo=repo_name,
521 group_name=group_name,
522 perm=perm)
523
498 524 Session.commit()
499 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 527 perm, group_name, repo_name
502 528 )
503 529 )
504 530 except Exception:
505 531 log.error(traceback.format_exc())
506 532 raise JSONRPCError(
507 'failed to edit permission %(repo)s for %(usergr)s' % dict(
508 usergr=group_name, repo=repo_name
533 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
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 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.home
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Home controller for Rhodecode
7 7
8 8 :created_on: Feb 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27
28 28 from pylons import tmpl_context as c, request
29 29 from paste.httpexceptions import HTTPBadRequest
30 30
31 31 from rhodecode.lib.auth import LoginRequired
32 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 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 class HomeController(BaseController):
39 39
40 40 @LoginRequired()
41 41 def __before__(self):
42 42 super(HomeController, self).__before__()
43 43
44 44 def index(self):
45
46 45 c.repos_list = self.scm_model.get_repos()
47
48 c.groups = RepoGroup.query()\
49 .filter(RepoGroup.group_parent_id == None).all()
46 c.groups = self.scm_model.get_repos_groups()
50 47
51 48 return render('/index.html')
52 49
53 50 def repo_switcher(self):
54 51 if request.is_xhr:
55 52 all_repos = Repository.query().order_by(Repository.repo_name).all()
56 53 c.repos_list = self.scm_model.get_repos(all_repos,
57 54 sort_key='name_sort')
58 55 return render('/repo_switcher_list.html')
59 56 else:
60 57 return HTTPBadRequest()
61 58
62 59 def branch_tag_switcher(self, repo_name):
63 60 if request.is_xhr:
64 61 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
65 62 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
66 63 return render('/switch_to_list.html')
67 64 else:
68 65 return HTTPBadRequest()
@@ -1,450 +1,454 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Some simple helper functions
7 7
8 8 :created_on: Jan 5, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import re
28 from vcs.utils.lazy import LazyProperty
29
28 30
29 31 def __get_lem():
30 32 from pygments import lexers
31 33 from string import lower
32 34 from collections import defaultdict
33 35
34 36 d = defaultdict(lambda: [])
35 37
36 38 def __clean(s):
37 39 s = s.lstrip('*')
38 40 s = s.lstrip('.')
39 41
40 42 if s.find('[') != -1:
41 43 exts = []
42 44 start, stop = s.find('['), s.find(']')
43 45
44 46 for suffix in s[start + 1:stop]:
45 47 exts.append(s[:s.find('[')] + suffix)
46 48 return map(lower, exts)
47 49 else:
48 50 return map(lower, [s])
49 51
50 52 for lx, t in sorted(lexers.LEXERS.items()):
51 53 m = map(__clean, t[-2])
52 54 if m:
53 55 m = reduce(lambda x, y: x + y, m)
54 56 for ext in m:
55 57 desc = lx.replace('Lexer', '')
56 58 d[ext].append(desc)
57 59
58 60 return dict(d)
59 61
60 62 # language map is also used by whoosh indexer, which for those specified
61 63 # extensions will index it's content
62 64 LANGUAGES_EXTENSIONS_MAP = __get_lem()
63 65
64 66 # Additional mappings that are not present in the pygments lexers
65 67 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
66 68 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
67 69
68 70 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
69 71
70 72 # list of readme files to search in file tree and display in summary
71 73 # attached weights defines the search order lower is first
72 74 ALL_READMES = [
73 75 ('readme', 0), ('README', 0), ('Readme', 0),
74 76 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
75 77 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
76 78 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
77 79 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
78 80 ]
79 81
80 82 # extension together with weights to search lower is first
81 83 RST_EXTS = [
82 84 ('', 0), ('.rst', 1), ('.rest', 1),
83 85 ('.RST', 2) , ('.REST', 2),
84 86 ('.txt', 3), ('.TXT', 3)
85 87 ]
86 88
87 89 MARKDOWN_EXTS = [
88 90 ('.md', 1), ('.MD', 1),
89 91 ('.mkdn', 2), ('.MKDN', 2),
90 92 ('.mdown', 3), ('.MDOWN', 3),
91 93 ('.markdown', 4), ('.MARKDOWN', 4)
92 94 ]
93 95
94 96 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
95 97
96 98 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
97 99
98 100
99 101 def str2bool(_str):
100 102 """
101 103 returs True/False value from given string, it tries to translate the
102 104 string into boolean
103 105
104 106 :param _str: string value to translate into boolean
105 107 :rtype: boolean
106 108 :returns: boolean from given string
107 109 """
108 110 if _str is None:
109 111 return False
110 112 if _str in (True, False):
111 113 return _str
112 114 _str = str(_str).strip().lower()
113 115 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
114 116
115 117
116 118 def convert_line_endings(line, mode):
117 119 """
118 120 Converts a given line "line end" accordingly to given mode
119 121
120 122 Available modes are::
121 123 0 - Unix
122 124 1 - Mac
123 125 2 - DOS
124 126
125 127 :param line: given line to convert
126 128 :param mode: mode to convert to
127 129 :rtype: str
128 130 :return: converted line according to mode
129 131 """
130 132 from string import replace
131 133
132 134 if mode == 0:
133 135 line = replace(line, '\r\n', '\n')
134 136 line = replace(line, '\r', '\n')
135 137 elif mode == 1:
136 138 line = replace(line, '\r\n', '\r')
137 139 line = replace(line, '\n', '\r')
138 140 elif mode == 2:
139 141 import re
140 142 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
141 143 return line
142 144
143 145
144 146 def detect_mode(line, default):
145 147 """
146 148 Detects line break for given line, if line break couldn't be found
147 149 given default value is returned
148 150
149 151 :param line: str line
150 152 :param default: default
151 153 :rtype: int
152 154 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
153 155 """
154 156 if line.endswith('\r\n'):
155 157 return 2
156 158 elif line.endswith('\n'):
157 159 return 0
158 160 elif line.endswith('\r'):
159 161 return 1
160 162 else:
161 163 return default
162 164
163 165
164 166 def generate_api_key(username, salt=None):
165 167 """
166 168 Generates unique API key for given username, if salt is not given
167 169 it'll be generated from some random string
168 170
169 171 :param username: username as string
170 172 :param salt: salt to hash generate KEY
171 173 :rtype: str
172 174 :returns: sha1 hash from username+salt
173 175 """
174 176 from tempfile import _RandomNameSequence
175 177 import hashlib
176 178
177 179 if salt is None:
178 180 salt = _RandomNameSequence().next()
179 181
180 182 return hashlib.sha1(username + salt).hexdigest()
181 183
182 184
183 185 def safe_unicode(str_, from_encoding='utf8'):
184 186 """
185 187 safe unicode function. Does few trick to turn str_ into unicode
186 188
187 189 In case of UnicodeDecode error we try to return it with encoding detected
188 190 by chardet library if it fails fallback to unicode with errors replaced
189 191
190 192 :param str_: string to decode
191 193 :rtype: unicode
192 194 :returns: unicode object
193 195 """
194 196 if isinstance(str_, unicode):
195 197 return str_
196 198
197 199 try:
198 200 return unicode(str_)
199 201 except UnicodeDecodeError:
200 202 pass
201 203
202 204 try:
203 205 return unicode(str_, from_encoding)
204 206 except UnicodeDecodeError:
205 207 pass
206 208
207 209 try:
208 210 import chardet
209 211 encoding = chardet.detect(str_)['encoding']
210 212 if encoding is None:
211 213 raise Exception()
212 214 return str_.decode(encoding)
213 215 except (ImportError, UnicodeDecodeError, Exception):
214 216 return unicode(str_, from_encoding, 'replace')
215 217
218
216 219 def safe_str(unicode_, to_encoding='utf8'):
217 220 """
218 221 safe str function. Does few trick to turn unicode_ into string
219 222
220 223 In case of UnicodeEncodeError we try to return it with encoding detected
221 224 by chardet library if it fails fallback to string with errors replaced
222 225
223 226 :param unicode_: unicode to encode
224 227 :rtype: str
225 228 :returns: str object
226 229 """
227 230
228 231 if not isinstance(unicode_, basestring):
229 232 return str(unicode_)
230 233
231 234 if isinstance(unicode_, str):
232 235 return unicode_
233 236
234 237 try:
235 238 return unicode_.encode(to_encoding)
236 239 except UnicodeEncodeError:
237 240 pass
238 241
239 242 try:
240 243 import chardet
241 244 encoding = chardet.detect(unicode_)['encoding']
242 245 print encoding
243 246 if encoding is None:
244 247 raise UnicodeEncodeError()
245 248
246 249 return unicode_.encode(encoding)
247 250 except (ImportError, UnicodeEncodeError):
248 251 return unicode_.encode(to_encoding, 'replace')
249 252
250 253 return safe_str
251 254
252 255
253
254 256 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
255 257 """
256 258 Custom engine_from_config functions that makes sure we use NullPool for
257 259 file based sqlite databases. This prevents errors on sqlite. This only
258 260 applies to sqlalchemy versions < 0.7.0
259 261
260 262 """
261 263 import sqlalchemy
262 264 from sqlalchemy import engine_from_config as efc
263 265 import logging
264 266
265 267 if int(sqlalchemy.__version__.split('.')[1]) < 7:
266 268
267 269 # This solution should work for sqlalchemy < 0.7.0, and should use
268 270 # proxy=TimerProxy() for execution time profiling
269 271
270 272 from sqlalchemy.pool import NullPool
271 273 url = configuration[prefix + 'url']
272 274
273 275 if url.startswith('sqlite'):
274 276 kwargs.update({'poolclass': NullPool})
275 277 return efc(configuration, prefix, **kwargs)
276 278 else:
277 279 import time
278 280 from sqlalchemy import event
279 281 from sqlalchemy.engine import Engine
280 282
281 283 log = logging.getLogger('sqlalchemy.engine')
282 284 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
283 285 engine = efc(configuration, prefix, **kwargs)
284 286
285 287 def color_sql(sql):
286 288 COLOR_SEQ = "\033[1;%dm"
287 289 COLOR_SQL = YELLOW
288 290 normal = '\x1b[0m'
289 291 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
290 292
291 293 if configuration['debug']:
292 294 #attach events only for debug configuration
293 295
294 296 def before_cursor_execute(conn, cursor, statement,
295 297 parameters, context, executemany):
296 298 context._query_start_time = time.time()
297 299 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
298 300
299 301
300 302 def after_cursor_execute(conn, cursor, statement,
301 303 parameters, context, executemany):
302 304 total = time.time() - context._query_start_time
303 305 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
304 306
305 307 event.listen(engine, "before_cursor_execute",
306 308 before_cursor_execute)
307 309 event.listen(engine, "after_cursor_execute",
308 310 after_cursor_execute)
309 311
310 312 return engine
311 313
312 314
313 315 def age(curdate):
314 316 """
315 317 turns a datetime into an age string.
316 318
317 319 :param curdate: datetime object
318 320 :rtype: unicode
319 321 :returns: unicode words describing age
320 322 """
321 323
322 324 from datetime import datetime
323 325 from webhelpers.date import time_ago_in_words
324 326
325 327 _ = lambda s:s
326 328
327 329 if not curdate:
328 330 return ''
329 331
330 332 agescales = [(_(u"year"), 3600 * 24 * 365),
331 333 (_(u"month"), 3600 * 24 * 30),
332 334 (_(u"day"), 3600 * 24),
333 335 (_(u"hour"), 3600),
334 336 (_(u"minute"), 60),
335 337 (_(u"second"), 1), ]
336 338
337 339 age = datetime.now() - curdate
338 340 age_seconds = (age.days * agescales[2][1]) + age.seconds
339 341 pos = 1
340 342 for scale in agescales:
341 343 if scale[1] <= age_seconds:
342 344 if pos == 6:pos = 5
343 345 return '%s %s' % (time_ago_in_words(curdate,
344 346 agescales[pos][0]), _('ago'))
345 347 pos += 1
346 348
347 349 return _(u'just now')
348 350
349 351
350 352 def uri_filter(uri):
351 353 """
352 354 Removes user:password from given url string
353 355
354 356 :param uri:
355 357 :rtype: unicode
356 358 :returns: filtered list of strings
357 359 """
358 360 if not uri:
359 361 return ''
360 362
361 363 proto = ''
362 364
363 365 for pat in ('https://', 'http://'):
364 366 if uri.startswith(pat):
365 367 uri = uri[len(pat):]
366 368 proto = pat
367 369 break
368 370
369 371 # remove passwords and username
370 372 uri = uri[uri.find('@') + 1:]
371 373
372 374 # get the port
373 375 cred_pos = uri.find(':')
374 376 if cred_pos == -1:
375 377 host, port = uri, None
376 378 else:
377 379 host, port = uri[:cred_pos], uri[cred_pos + 1:]
378 380
379 381 return filter(None, [proto, host, port])
380 382
381 383
382 384 def credentials_filter(uri):
383 385 """
384 386 Returns a url with removed credentials
385 387
386 388 :param uri:
387 389 """
388 390
389 391 uri = uri_filter(uri)
390 392 #check if we have port
391 393 if len(uri) > 2 and uri[2]:
392 394 uri[2] = ':' + uri[2]
393 395
394 396 return ''.join(uri)
395 397
398
396 399 def get_changeset_safe(repo, rev):
397 400 """
398 401 Safe version of get_changeset if this changeset doesn't exists for a
399 402 repo it returns a Dummy one instead
400 403
401 404 :param repo:
402 405 :param rev:
403 406 """
404 407 from vcs.backends.base import BaseRepository
405 408 from vcs.exceptions import RepositoryError
406 409 if not isinstance(repo, BaseRepository):
407 410 raise Exception('You must pass an Repository '
408 411 'object as first argument got %s', type(repo))
409 412
410 413 try:
411 414 cs = repo.get_changeset(rev)
412 415 except RepositoryError:
413 416 from rhodecode.lib.utils import EmptyChangeset
414 417 cs = EmptyChangeset(requested_revision=rev)
415 418 return cs
416 419
417 420
418 421 def get_current_revision(quiet=False):
419 422 """
420 423 Returns tuple of (number, id) from repository containing this package
421 424 or None if repository could not be found.
422 425
423 426 :param quiet: prints error for fetching revision if True
424 427 """
425 428
426 429 try:
427 430 from vcs import get_repo
428 431 from vcs.utils.helpers import get_scm
429 432 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
430 433 scm = get_scm(repopath)[0]
431 434 repo = get_repo(path=repopath, alias=scm)
432 435 tip = repo.get_changeset()
433 436 return (tip.revision, tip.short_id)
434 437 except Exception, err:
435 438 if not quiet:
436 439 print ("Cannot retrieve rhodecode's revision. Original error "
437 440 "was: %s" % err)
438 441 return None
439 442
443
440 444 def extract_mentioned_users(s):
441 445 """
442 446 Returns unique usernames from given string s that have @mention
443 447
444 448 :param s: string to get mentions
445 449 """
446 450 usrs = {}
447 451 for username in re.findall(r'(?:^@|\s@)(\w+)', s):
448 452 usrs[username] = username
449 453
450 454 return sorted(usrs.keys())
@@ -1,701 +1,774 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.auth
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 authentication and permission libraries
7 7
8 8 :created_on: Apr 4, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import random
27 27 import logging
28 28 import traceback
29 29 import hashlib
30 30
31 31 from tempfile import _RandomNameSequence
32 32 from decorator import decorator
33 33
34 from pylons import config, session, url, request
34 from pylons import config, url, request
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
39 39 from rhodecode.model.meta import Session
40 40
41 41 if __platform__ in PLATFORM_WIN:
42 42 from hashlib import sha256
43 43 if __platform__ in PLATFORM_OTHERS:
44 44 import bcrypt
45 45
46 46 from rhodecode.lib import str2bool, safe_unicode
47 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 49 from rhodecode.lib.auth_ldap import AuthLdap
50 50
51 51 from rhodecode.model import meta
52 52 from rhodecode.model.user import UserModel
53 53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class PasswordGenerator(object):
59 59 """
60 60 This is a simple class for generating password from different sets of
61 61 characters
62 62 usage::
63 63
64 64 passwd_gen = PasswordGenerator()
65 65 #print 8-letter password containing only big and small letters
66 66 of alphabet
67 67 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
68 68 """
69 69 ALPHABETS_NUM = r'''1234567890'''
70 70 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
71 71 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
72 72 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
73 73 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
74 74 + ALPHABETS_NUM + ALPHABETS_SPECIAL
75 75 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
76 76 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
77 77 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
78 78 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
79 79
80 80 def __init__(self, passwd=''):
81 81 self.passwd = passwd
82 82
83 def gen_password(self, len, type):
84 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
83 def gen_password(self, length, type_):
84 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
85 85 return self.passwd
86 86
87 87
88 88 class RhodeCodeCrypto(object):
89 89
90 90 @classmethod
91 91 def hash_string(cls, str_):
92 92 """
93 93 Cryptographic function used for password hashing based on pybcrypt
94 94 or pycrypto in windows
95 95
96 96 :param password: password to hash
97 97 """
98 98 if __platform__ in PLATFORM_WIN:
99 99 return sha256(str_).hexdigest()
100 100 elif __platform__ in PLATFORM_OTHERS:
101 101 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
102 102 else:
103 103 raise Exception('Unknown or unsupported platform %s' \
104 104 % __platform__)
105 105
106 106 @classmethod
107 107 def hash_check(cls, password, hashed):
108 108 """
109 109 Checks matching password with it's hashed value, runs different
110 110 implementation based on platform it runs on
111 111
112 112 :param password: password
113 113 :param hashed: password in hashed form
114 114 """
115 115
116 116 if __platform__ in PLATFORM_WIN:
117 117 return sha256(password).hexdigest() == hashed
118 118 elif __platform__ in PLATFORM_OTHERS:
119 119 return bcrypt.hashpw(password, hashed) == hashed
120 120 else:
121 121 raise Exception('Unknown or unsupported platform %s' \
122 122 % __platform__)
123 123
124 124
125 125 def get_crypt_password(password):
126 126 return RhodeCodeCrypto.hash_string(password)
127 127
128 128
129 129 def check_password(password, hashed):
130 130 return RhodeCodeCrypto.hash_check(password, hashed)
131 131
132 132
133 133 def generate_api_key(str_, salt=None):
134 134 """
135 135 Generates API KEY from given string
136 136
137 137 :param str_:
138 138 :param salt:
139 139 """
140 140
141 141 if salt is None:
142 142 salt = _RandomNameSequence().next()
143 143
144 144 return hashlib.sha1(str_ + salt).hexdigest()
145 145
146 146
147 147 def authfunc(environ, username, password):
148 148 """
149 149 Dummy authentication wrapper function used in Mercurial and Git for
150 150 access control.
151 151
152 152 :param environ: needed only for using in Basic auth
153 153 """
154 154 return authenticate(username, password)
155 155
156 156
157 157 def authenticate(username, password):
158 158 """
159 159 Authentication function used for access control,
160 160 firstly checks for db authentication then if ldap is enabled for ldap
161 161 authentication, also creates ldap user if not in database
162 162
163 163 :param username: username
164 164 :param password: password
165 165 """
166 166
167 167 user_model = UserModel()
168 168 user = User.get_by_username(username)
169 169
170 170 log.debug('Authenticating user using RhodeCode account')
171 171 if user is not None and not user.ldap_dn:
172 172 if user.active:
173 173 if user.username == 'default' and user.active:
174 174 log.info('user %s authenticated correctly as anonymous user',
175 175 username)
176 176 return True
177 177
178 178 elif user.username == username and check_password(password,
179 179 user.password):
180 180 log.info('user %s authenticated correctly' % username)
181 181 return True
182 182 else:
183 183 log.warning('user %s is disabled' % username)
184 184
185 185 else:
186 186 log.debug('Regular authentication failed')
187 187 user_obj = User.get_by_username(username, case_insensitive=True)
188 188
189 189 if user_obj is not None and not user_obj.ldap_dn:
190 190 log.debug('this user already exists as non ldap')
191 191 return False
192 192
193 193 ldap_settings = RhodeCodeSetting.get_ldap_settings()
194 194 #======================================================================
195 195 # FALLBACK TO LDAP AUTH IF ENABLE
196 196 #======================================================================
197 197 if str2bool(ldap_settings.get('ldap_active')):
198 198 log.debug("Authenticating user using ldap")
199 199 kwargs = {
200 200 'server': ldap_settings.get('ldap_host', ''),
201 201 'base_dn': ldap_settings.get('ldap_base_dn', ''),
202 202 'port': ldap_settings.get('ldap_port'),
203 203 'bind_dn': ldap_settings.get('ldap_dn_user'),
204 204 'bind_pass': ldap_settings.get('ldap_dn_pass'),
205 205 'tls_kind': ldap_settings.get('ldap_tls_kind'),
206 206 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
207 207 'ldap_filter': ldap_settings.get('ldap_filter'),
208 208 'search_scope': ldap_settings.get('ldap_search_scope'),
209 209 'attr_login': ldap_settings.get('ldap_attr_login'),
210 210 'ldap_version': 3,
211 211 }
212 212 log.debug('Checking for ldap authentication')
213 213 try:
214 214 aldap = AuthLdap(**kwargs)
215 215 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
216 216 password)
217 217 log.debug('Got ldap DN response %s' % user_dn)
218 218
219 219 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
220 220 .get(k), [''])[0]
221 221
222 222 user_attrs = {
223 223 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
224 224 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
225 225 'email': get_ldap_attr('ldap_attr_email'),
226 226 }
227 227
228 228 if user_model.create_ldap(username, password, user_dn,
229 229 user_attrs):
230 230 log.info('created new ldap user %s' % username)
231 231
232 232 Session.commit()
233 233 return True
234 234 except (LdapUsernameError, LdapPasswordError,):
235 235 pass
236 236 except (Exception,):
237 237 log.error(traceback.format_exc())
238 238 pass
239 239 return False
240 240
241 241
242 242 def login_container_auth(username):
243 243 user = User.get_by_username(username)
244 244 if user is None:
245 245 user_attrs = {
246 246 'name': username,
247 247 'lastname': None,
248 248 'email': None,
249 249 }
250 250 user = UserModel().create_for_container_auth(username, user_attrs)
251 251 if not user:
252 252 return None
253 253 log.info('User %s was created by container authentication' % username)
254 254
255 255 if not user.active:
256 256 return None
257 257
258 258 user.update_lastlogin()
259 259 Session.commit()
260 260
261 261 log.debug('User %s is now logged in by container authentication',
262 262 user.username)
263 263 return user
264 264
265 265
266 266 def get_container_username(environ, config):
267 267 username = None
268 268
269 269 if str2bool(config.get('container_auth_enabled', False)):
270 270 from paste.httpheaders import REMOTE_USER
271 271 username = REMOTE_USER(environ)
272 272
273 273 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
274 274 username = environ.get('HTTP_X_FORWARDED_USER')
275 275
276 276 if username:
277 277 # Removing realm and domain from username
278 278 username = username.partition('@')[0]
279 279 username = username.rpartition('\\')[2]
280 280 log.debug('Received username %s from container' % username)
281 281
282 282 return username
283 283
284 284
285 285 class AuthUser(object):
286 286 """
287 287 A simple object that handles all attributes of user in RhodeCode
288 288
289 289 It does lookup based on API key,given user, or user present in session
290 290 Then it fills all required information for such user. It also checks if
291 291 anonymous access is enabled and if so, it returns default user as logged
292 292 in
293 293 """
294 294
295 295 def __init__(self, user_id=None, api_key=None, username=None):
296 296
297 297 self.user_id = user_id
298 298 self.api_key = None
299 299 self.username = username
300 300
301 301 self.name = ''
302 302 self.lastname = ''
303 303 self.email = ''
304 304 self.is_authenticated = False
305 305 self.admin = False
306 306 self.permissions = {}
307 307 self._api_key = api_key
308 308 self.propagate_data()
309 309 self._instance = None
310 310
311 311 def propagate_data(self):
312 312 user_model = UserModel()
313 313 self.anonymous_user = User.get_by_username('default', cache=True)
314 314 is_user_loaded = False
315 315
316 316 # try go get user by api key
317 317 if self._api_key and self._api_key != self.anonymous_user.api_key:
318 318 log.debug('Auth User lookup by API KEY %s' % self._api_key)
319 319 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
320 320 # lookup by userid
321 321 elif (self.user_id is not None and
322 322 self.user_id != self.anonymous_user.user_id):
323 323 log.debug('Auth User lookup by USER ID %s' % self.user_id)
324 324 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
325 325 # lookup by username
326 326 elif self.username and \
327 327 str2bool(config.get('container_auth_enabled', False)):
328 328
329 329 log.debug('Auth User lookup by USER NAME %s' % self.username)
330 330 dbuser = login_container_auth(self.username)
331 331 if dbuser is not None:
332 332 for k, v in dbuser.get_dict().items():
333 333 setattr(self, k, v)
334 334 self.set_authenticated()
335 335 is_user_loaded = True
336 336
337 337 if not is_user_loaded:
338 338 # if we cannot authenticate user try anonymous
339 339 if self.anonymous_user.active is True:
340 340 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
341 341 # then we set this user is logged in
342 342 self.is_authenticated = True
343 343 else:
344 344 self.user_id = None
345 345 self.username = None
346 346 self.is_authenticated = False
347 347
348 348 if not self.username:
349 349 self.username = 'None'
350 350
351 351 log.debug('Auth User is now %s' % self)
352 352 user_model.fill_perms(self)
353 353
354 354 @property
355 355 def is_admin(self):
356 356 return self.admin
357 357
358 358 def __repr__(self):
359 359 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
360 360 self.is_authenticated)
361 361
362 362 def set_authenticated(self, authenticated=True):
363 363 if self.user_id != self.anonymous_user.user_id:
364 364 self.is_authenticated = authenticated
365 365
366 366 def get_cookie_store(self):
367 367 return {'username': self.username,
368 368 'user_id': self.user_id,
369 369 'is_authenticated': self.is_authenticated}
370 370
371 371 @classmethod
372 372 def from_cookie_store(cls, cookie_store):
373 373 user_id = cookie_store.get('user_id')
374 374 username = cookie_store.get('username')
375 375 api_key = cookie_store.get('api_key')
376 376 return AuthUser(user_id, api_key, username)
377 377
378 378
379 379 def set_available_permissions(config):
380 380 """
381 381 This function will propagate pylons globals with all available defined
382 382 permission given in db. We don't want to check each time from db for new
383 383 permissions since adding a new permission also requires application restart
384 384 ie. to decorate new views with the newly created permission
385 385
386 386 :param config: current pylons config instance
387 387
388 388 """
389 389 log.info('getting information about all available permissions')
390 390 try:
391 391 sa = meta.Session
392 392 all_perms = sa.query(Permission).all()
393 393 except Exception:
394 394 pass
395 395 finally:
396 396 meta.Session.remove()
397 397
398 398 config['available_permissions'] = [x.permission_name for x in all_perms]
399 399
400 400
401 401 #==============================================================================
402 402 # CHECK DECORATORS
403 403 #==============================================================================
404 404 class LoginRequired(object):
405 405 """
406 406 Must be logged in to execute this function else
407 407 redirect to login page
408 408
409 409 :param api_access: if enabled this checks only for valid auth token
410 410 and grants access based on valid token
411 411 """
412 412
413 413 def __init__(self, api_access=False):
414 414 self.api_access = api_access
415 415
416 416 def __call__(self, func):
417 417 return decorator(self.__wrapper, func)
418 418
419 419 def __wrapper(self, func, *fargs, **fkwargs):
420 420 cls = fargs[0]
421 421 user = cls.rhodecode_user
422 422
423 423 api_access_ok = False
424 424 if self.api_access:
425 425 log.debug('Checking API KEY access for %s' % cls)
426 426 if user.api_key == request.GET.get('api_key'):
427 427 api_access_ok = True
428 428 else:
429 429 log.debug("API KEY token not valid")
430 430
431 431 log.debug('Checking if %s is authenticated @ %s' % (user.username, cls))
432 432 if user.is_authenticated or api_access_ok:
433 433 log.debug('user %s is authenticated' % user.username)
434 434 return func(*fargs, **fkwargs)
435 435 else:
436 436 log.warn('user %s NOT authenticated' % user.username)
437 437 p = url.current()
438 438
439 439 log.debug('redirecting to login page with %s' % p)
440 440 return redirect(url('login_home', came_from=p))
441 441
442 442
443 443 class NotAnonymous(object):
444 444 """
445 445 Must be logged in to execute this function else
446 446 redirect to login page"""
447 447
448 448 def __call__(self, func):
449 449 return decorator(self.__wrapper, func)
450 450
451 451 def __wrapper(self, func, *fargs, **fkwargs):
452 452 cls = fargs[0]
453 453 self.user = cls.rhodecode_user
454 454
455 455 log.debug('Checking if user is not anonymous @%s' % cls)
456 456
457 457 anonymous = self.user.username == 'default'
458 458
459 459 if anonymous:
460 460 p = url.current()
461 461
462 462 import rhodecode.lib.helpers as h
463 463 h.flash(_('You need to be a registered user to '
464 464 'perform this action'),
465 465 category='warning')
466 466 return redirect(url('login_home', came_from=p))
467 467 else:
468 468 return func(*fargs, **fkwargs)
469 469
470 470
471 471 class PermsDecorator(object):
472 472 """Base class for controller decorators"""
473 473
474 474 def __init__(self, *required_perms):
475 475 available_perms = config['available_permissions']
476 476 for perm in required_perms:
477 477 if perm not in available_perms:
478 478 raise Exception("'%s' permission is not defined" % perm)
479 479 self.required_perms = set(required_perms)
480 480 self.user_perms = None
481 481
482 482 def __call__(self, func):
483 483 return decorator(self.__wrapper, func)
484 484
485 485 def __wrapper(self, func, *fargs, **fkwargs):
486 486 cls = fargs[0]
487 487 self.user = cls.rhodecode_user
488 488 self.user_perms = self.user.permissions
489 489 log.debug('checking %s permissions %s for %s %s',
490 490 self.__class__.__name__, self.required_perms, cls,
491 491 self.user)
492 492
493 493 if self.check_permissions():
494 494 log.debug('Permission granted for %s %s' % (cls, self.user))
495 495 return func(*fargs, **fkwargs)
496 496
497 497 else:
498 498 log.warning('Permission denied for %s %s' % (cls, self.user))
499 499 anonymous = self.user.username == 'default'
500 500
501 501 if anonymous:
502 502 p = url.current()
503 503
504 504 import rhodecode.lib.helpers as h
505 505 h.flash(_('You need to be a signed in to '
506 506 'view this page'),
507 507 category='warning')
508 508 return redirect(url('login_home', came_from=p))
509 509
510 510 else:
511 511 # redirect with forbidden ret code
512 512 return abort(403)
513 513
514 514 def check_permissions(self):
515 515 """Dummy function for overriding"""
516 516 raise Exception('You have to write this function in child class')
517 517
518 518
519 519 class HasPermissionAllDecorator(PermsDecorator):
520 520 """
521 521 Checks for access permission for all given predicates. All of them
522 522 have to be meet in order to fulfill the request
523 523 """
524 524
525 525 def check_permissions(self):
526 526 if self.required_perms.issubset(self.user_perms.get('global')):
527 527 return True
528 528 return False
529 529
530 530
531 531 class HasPermissionAnyDecorator(PermsDecorator):
532 532 """
533 533 Checks for access permission for any of given predicates. In order to
534 534 fulfill the request any of predicates must be meet
535 535 """
536 536
537 537 def check_permissions(self):
538 538 if self.required_perms.intersection(self.user_perms.get('global')):
539 539 return True
540 540 return False
541 541
542 542
543 543 class HasRepoPermissionAllDecorator(PermsDecorator):
544 544 """
545 545 Checks for access permission for all given predicates for specific
546 546 repository. All of them have to be meet in order to fulfill the request
547 547 """
548 548
549 549 def check_permissions(self):
550 550 repo_name = get_repo_slug(request)
551 551 try:
552 552 user_perms = set([self.user_perms['repositories'][repo_name]])
553 553 except KeyError:
554 554 return False
555 555 if self.required_perms.issubset(user_perms):
556 556 return True
557 557 return False
558 558
559 559
560 560 class HasRepoPermissionAnyDecorator(PermsDecorator):
561 561 """
562 562 Checks for access permission for any of given predicates for specific
563 563 repository. In order to fulfill the request any of predicates must be meet
564 564 """
565 565
566 566 def check_permissions(self):
567 567 repo_name = get_repo_slug(request)
568 568
569 569 try:
570 570 user_perms = set([self.user_perms['repositories'][repo_name]])
571 571 except KeyError:
572 572 return False
573 573 if self.required_perms.intersection(user_perms):
574 574 return True
575 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 614 # CHECK FUNCTIONS
580 615 #==============================================================================
581 616 class PermsFunction(object):
582 617 """Base function for other check functions"""
583 618
584 619 def __init__(self, *perms):
585 620 available_perms = config['available_permissions']
586 621
587 622 for perm in perms:
588 623 if perm not in available_perms:
589 624 raise Exception("'%s' permission in not defined" % perm)
590 625 self.required_perms = set(perms)
591 626 self.user_perms = None
592 627 self.granted_for = ''
593 628 self.repo_name = None
594 629
595 630 def __call__(self, check_Location=''):
596 631 user = request.user
597 632 if not user:
598 633 return False
599 634 self.user_perms = user.permissions
600 635 self.granted_for = user
601 636 log.debug('checking %s %s %s', self.__class__.__name__,
602 637 self.required_perms, user)
603 638
604 639 if self.check_permissions():
605 640 log.debug('Permission granted %s @ %s', self.granted_for,
606 641 check_Location or 'unspecified location')
607 642 return True
608 643
609 644 else:
610 645 log.warning('Permission denied for %s @ %s', self.granted_for,
611 646 check_Location or 'unspecified location')
612 647 return False
613 648
614 649 def check_permissions(self):
615 650 """Dummy function for overriding"""
616 651 raise Exception('You have to write this function in child class')
617 652
618 653
619 654 class HasPermissionAll(PermsFunction):
620 655 def check_permissions(self):
621 656 if self.required_perms.issubset(self.user_perms.get('global')):
622 657 return True
623 658 return False
624 659
625 660
626 661 class HasPermissionAny(PermsFunction):
627 662 def check_permissions(self):
628 663 if self.required_perms.intersection(self.user_perms.get('global')):
629 664 return True
630 665 return False
631 666
632 667
633 668 class HasRepoPermissionAll(PermsFunction):
634 669
635 670 def __call__(self, repo_name=None, check_Location=''):
636 671 self.repo_name = repo_name
637 672 return super(HasRepoPermissionAll, self).__call__(check_Location)
638 673
639 674 def check_permissions(self):
640 675 if not self.repo_name:
641 676 self.repo_name = get_repo_slug(request)
642 677
643 678 try:
644 self.user_perms = set([self.user_perms['reposit'
645 'ories'][self.repo_name]])
679 self.user_perms = set(
680 [self.user_perms['repositories'][self.repo_name]]
681 )
646 682 except KeyError:
647 683 return False
648 684 self.granted_for = self.repo_name
649 685 if self.required_perms.issubset(self.user_perms):
650 686 return True
651 687 return False
652 688
653 689
654 690 class HasRepoPermissionAny(PermsFunction):
655 691
656 692 def __call__(self, repo_name=None, check_Location=''):
657 693 self.repo_name = repo_name
658 694 return super(HasRepoPermissionAny, self).__call__(check_Location)
659 695
660 696 def check_permissions(self):
661 697 if not self.repo_name:
662 698 self.repo_name = get_repo_slug(request)
663 699
664 700 try:
665 self.user_perms = set([self.user_perms['reposi'
666 'tories'][self.repo_name]])
701 self.user_perms = set(
702 [self.user_perms['repositories'][self.repo_name]]
703 )
667 704 except KeyError:
668 705 return False
669 706 self.granted_for = self.repo_name
670 707 if self.required_perms.intersection(self.user_perms):
671 708 return True
672 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 749 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
677 750 #==============================================================================
678 751 class HasPermissionAnyMiddleware(object):
679 752 def __init__(self, *perms):
680 753 self.required_perms = set(perms)
681 754
682 755 def __call__(self, user, repo_name):
683 756 usr = AuthUser(user.user_id)
684 757 try:
685 758 self.user_perms = set([usr.permissions['repositories'][repo_name]])
686 759 except:
687 760 self.user_perms = set()
688 761 self.granted_for = ''
689 762 self.username = user.username
690 763 self.repo_name = repo_name
691 764 return self.check_permissions()
692 765
693 766 def check_permissions(self):
694 767 log.debug('checking mercurial protocol '
695 768 'permissions %s for user:%s repository:%s', self.user_perms,
696 769 self.username, self.repo_name)
697 770 if self.required_perms.intersection(self.user_perms):
698 771 log.debug('permission granted')
699 772 return True
700 773 log.debug('permission denied')
701 774 return False
@@ -1,495 +1,500 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode. Used for creation
7 7 of database as well as for migration operations
8 8
9 9 :created_on: Apr 10, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import sys
29 29 import uuid
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from rhodecode import __dbversion__
34 34 from rhodecode.model import meta
35 35
36 36 from rhodecode.model.user import UserModel
37 37 from rhodecode.lib.utils import ask_ok
38 38 from rhodecode.model import init_model
39 39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 40 RhodeCodeSetting, UserToPerm, DbMigrateVersion
41 41
42 42 from sqlalchemy.engine import create_engine
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class DbManage(object):
48 48 def __init__(self, log_sql, dbconf, root, tests=False):
49 49 self.dbname = dbconf.split('/')[-1]
50 50 self.tests = tests
51 51 self.root = root
52 52 self.dburi = dbconf
53 53 self.log_sql = log_sql
54 54 self.db_exists = False
55 55 self.init_db()
56 56
57 57 def init_db(self):
58 58 engine = create_engine(self.dburi, echo=self.log_sql)
59 59 init_model(engine)
60 60 self.sa = meta.Session
61 61
62 62 def create_tables(self, override=False):
63 63 """
64 64 Create a auth database
65 65 """
66 66
67 67 log.info("Any existing database is going to be destroyed")
68 68 if self.tests:
69 69 destroy = True
70 70 else:
71 71 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
72 72 if not destroy:
73 73 sys.exit()
74 74 if destroy:
75 75 meta.Base.metadata.drop_all()
76 76
77 77 checkfirst = not override
78 78 meta.Base.metadata.create_all(checkfirst=checkfirst)
79 79 log.info('Created tables for %s' % self.dbname)
80 80
81 81 def set_db_version(self):
82 82 ver = DbMigrateVersion()
83 83 ver.version = __dbversion__
84 84 ver.repository_id = 'rhodecode_db_migrations'
85 85 ver.repository_path = 'versions'
86 86 self.sa.add(ver)
87 87 log.info('db version set to: %s' % __dbversion__)
88 88
89 89 def upgrade(self):
90 90 """
91 91 Upgrades given database schema to given revision following
92 92 all needed steps, to perform the upgrade
93 93
94 94 """
95 95
96 96 from rhodecode.lib.dbmigrate.migrate.versioning import api
97 97 from rhodecode.lib.dbmigrate.migrate.exceptions import \
98 98 DatabaseNotControlledError
99 99
100 100 if 'sqlite' in self.dburi:
101 101 print (
102 102 '********************** WARNING **********************\n'
103 103 'Make sure your version of sqlite is at least 3.7.X. \n'
104 104 'Earlier versions are known to fail on some migrations\n'
105 105 '*****************************************************\n'
106 106 )
107 107 upgrade = ask_ok('You are about to perform database upgrade, make '
108 108 'sure You backed up your database before. '
109 109 'Continue ? [y/n]')
110 110 if not upgrade:
111 111 sys.exit('Nothing done')
112 112
113 113 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
114 114 'rhodecode/lib/dbmigrate')
115 115 db_uri = self.dburi
116 116
117 117 try:
118 118 curr_version = api.db_version(db_uri, repository_path)
119 119 msg = ('Found current database under version'
120 120 ' control with version %s' % curr_version)
121 121
122 122 except (RuntimeError, DatabaseNotControlledError):
123 123 curr_version = 1
124 124 msg = ('Current database is not under version control. Setting'
125 125 ' as version %s' % curr_version)
126 126 api.version_control(db_uri, repository_path, curr_version)
127 127
128 128 print (msg)
129 129
130 130 if curr_version == __dbversion__:
131 131 sys.exit('This database is already at the newest version')
132 132
133 133 #======================================================================
134 134 # UPGRADE STEPS
135 135 #======================================================================
136 136 class UpgradeSteps(object):
137 137 """
138 138 Those steps follow schema versions so for example schema
139 139 for example schema with seq 002 == step_2 and so on.
140 140 """
141 141
142 142 def __init__(self, klass):
143 143 self.klass = klass
144 144
145 145 def step_0(self):
146 146 # step 0 is the schema upgrade, and than follow proper upgrades
147 147 print ('attempting to do database upgrade to version %s' \
148 148 % __dbversion__)
149 149 api.upgrade(db_uri, repository_path, __dbversion__)
150 150 print ('Schema upgrade completed')
151 151
152 152 def step_1(self):
153 153 pass
154 154
155 155 def step_2(self):
156 156 print ('Patching repo paths for newer version of RhodeCode')
157 157 self.klass.fix_repo_paths()
158 158
159 159 print ('Patching default user of RhodeCode')
160 160 self.klass.fix_default_user()
161 161
162 162 log.info('Changing ui settings')
163 163 self.klass.create_ui_settings()
164 164
165 165 def step_3(self):
166 166 print ('Adding additional settings into RhodeCode db')
167 167 self.klass.fix_settings()
168 168 print ('Adding ldap defaults')
169 169 self.klass.create_ldap_options(skip_existing=True)
170 170
171 171 def step_4(self):
172 172 print ('TODO:')
173 173 raise NotImplementedError()
174 174
175 175 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
176 176
177 177 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
178 178 for step in upgrade_steps:
179 179 print ('performing upgrade step %s' % step)
180 180 getattr(UpgradeSteps(self), 'step_%s' % step)()
181 181
182 182 def fix_repo_paths(self):
183 183 """
184 184 Fixes a old rhodecode version path into new one without a '*'
185 185 """
186 186
187 187 paths = self.sa.query(RhodeCodeUi)\
188 188 .filter(RhodeCodeUi.ui_key == '/')\
189 189 .scalar()
190 190
191 191 paths.ui_value = paths.ui_value.replace('*', '')
192 192
193 193 try:
194 194 self.sa.add(paths)
195 195 self.sa.commit()
196 196 except:
197 197 self.sa.rollback()
198 198 raise
199 199
200 200 def fix_default_user(self):
201 201 """
202 202 Fixes a old default user with some 'nicer' default values,
203 203 used mostly for anonymous access
204 204 """
205 205 def_user = self.sa.query(User)\
206 206 .filter(User.username == 'default')\
207 207 .one()
208 208
209 209 def_user.name = 'Anonymous'
210 210 def_user.lastname = 'User'
211 211 def_user.email = 'anonymous@rhodecode.org'
212 212
213 213 try:
214 214 self.sa.add(def_user)
215 215 self.sa.commit()
216 216 except:
217 217 self.sa.rollback()
218 218 raise
219 219
220 220 def fix_settings(self):
221 221 """
222 222 Fixes rhodecode settings adds ga_code key for google analytics
223 223 """
224 224
225 225 hgsettings3 = RhodeCodeSetting('ga_code', '')
226 226
227 227 try:
228 228 self.sa.add(hgsettings3)
229 229 self.sa.commit()
230 230 except:
231 231 self.sa.rollback()
232 232 raise
233 233
234 234 def admin_prompt(self, second=False):
235 235 if not self.tests:
236 236 import getpass
237 237
238 238 def get_password():
239 239 password = getpass.getpass('Specify admin password '
240 240 '(min 6 chars):')
241 241 confirm = getpass.getpass('Confirm password:')
242 242
243 243 if password != confirm:
244 244 log.error('passwords mismatch')
245 245 return False
246 246 if len(password) < 6:
247 247 log.error('password is to short use at least 6 characters')
248 248 return False
249 249
250 250 return password
251 251
252 252 username = raw_input('Specify admin username:')
253 253
254 254 password = get_password()
255 255 if not password:
256 256 #second try
257 257 password = get_password()
258 258 if not password:
259 259 sys.exit()
260 260
261 261 email = raw_input('Specify admin email:')
262 262 self.create_user(username, password, email, True)
263 263 else:
264 264 log.info('creating admin and regular test users')
265 265 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
266 266 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
267 267 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
268 268 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
269 269 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
270 270
271 271 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
272 272 TEST_USER_ADMIN_EMAIL, True)
273 273
274 274 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
275 275 TEST_USER_REGULAR_EMAIL, False)
276 276
277 277 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
278 278 TEST_USER_REGULAR2_EMAIL, False)
279 279
280 280 def create_ui_settings(self):
281 281 """
282 282 Creates ui settings, fills out hooks
283 283 and disables dotencode
284 284 """
285 285
286 286 #HOOKS
287 287 hooks1_key = RhodeCodeUi.HOOK_UPDATE
288 288 hooks1_ = self.sa.query(RhodeCodeUi)\
289 289 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
290 290
291 291 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
292 292 hooks1.ui_section = 'hooks'
293 293 hooks1.ui_key = hooks1_key
294 294 hooks1.ui_value = 'hg update >&2'
295 295 hooks1.ui_active = False
296 296
297 297 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
298 298 hooks2_ = self.sa.query(RhodeCodeUi)\
299 299 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
300 300
301 301 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
302 302 hooks2.ui_section = 'hooks'
303 303 hooks2.ui_key = hooks2_key
304 304 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
305 305
306 306 hooks3 = RhodeCodeUi()
307 307 hooks3.ui_section = 'hooks'
308 308 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
309 309 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
310 310
311 311 hooks4 = RhodeCodeUi()
312 312 hooks4.ui_section = 'hooks'
313 313 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
314 314 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
315 315
316 316 # For mercurial 1.7 set backward comapatibility with format
317 317 dotencode_disable = RhodeCodeUi()
318 318 dotencode_disable.ui_section = 'format'
319 319 dotencode_disable.ui_key = 'dotencode'
320 320 dotencode_disable.ui_value = 'false'
321 321
322 322 # enable largefiles
323 323 largefiles = RhodeCodeUi()
324 324 largefiles.ui_section = 'extensions'
325 325 largefiles.ui_key = 'largefiles'
326 326 largefiles.ui_value = ''
327 327
328 328 self.sa.add(hooks1)
329 329 self.sa.add(hooks2)
330 330 self.sa.add(hooks3)
331 331 self.sa.add(hooks4)
332 332 self.sa.add(largefiles)
333 333
334 334 def create_ldap_options(self, skip_existing=False):
335 335 """Creates ldap settings"""
336 336
337 337 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
338 338 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
339 339 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
340 340 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
341 341 ('ldap_filter', ''), ('ldap_search_scope', ''),
342 342 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
343 343 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
344 344
345 345 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
346 346 log.debug('Skipping option %s' % k)
347 347 continue
348 348 setting = RhodeCodeSetting(k, v)
349 349 self.sa.add(setting)
350 350
351 351 def config_prompt(self, test_repo_path='', retries=3):
352 352 if retries == 3:
353 353 log.info('Setting up repositories config')
354 354
355 355 if not self.tests and not test_repo_path:
356 356 path = raw_input(
357 357 'Enter a valid path to store repositories. '
358 358 'All repositories in that path will be added automatically:'
359 359 )
360 360 else:
361 361 path = test_repo_path
362 362 path_ok = True
363 363
364 364 # check proper dir
365 365 if not os.path.isdir(path):
366 366 path_ok = False
367 367 log.error('Given path %s is not a valid directory' % path)
368 368
369 369 # check write access
370 370 if not os.access(path, os.W_OK) and path_ok:
371 371 path_ok = False
372 372 log.error('No write permission to given path %s' % path)
373 373
374 374 if retries == 0:
375 375 sys.exit('max retries reached')
376 376 if path_ok is False:
377 377 retries -= 1
378 378 return self.config_prompt(test_repo_path, retries)
379 379
380 380 return path
381 381
382 382 def create_settings(self, path):
383 383
384 384 self.create_ui_settings()
385 385
386 386 #HG UI OPTIONS
387 387 web1 = RhodeCodeUi()
388 388 web1.ui_section = 'web'
389 389 web1.ui_key = 'push_ssl'
390 390 web1.ui_value = 'false'
391 391
392 392 web2 = RhodeCodeUi()
393 393 web2.ui_section = 'web'
394 394 web2.ui_key = 'allow_archive'
395 395 web2.ui_value = 'gz zip bz2'
396 396
397 397 web3 = RhodeCodeUi()
398 398 web3.ui_section = 'web'
399 399 web3.ui_key = 'allow_push'
400 400 web3.ui_value = '*'
401 401
402 402 web4 = RhodeCodeUi()
403 403 web4.ui_section = 'web'
404 404 web4.ui_key = 'baseurl'
405 405 web4.ui_value = '/'
406 406
407 407 paths = RhodeCodeUi()
408 408 paths.ui_section = 'paths'
409 409 paths.ui_key = '/'
410 410 paths.ui_value = path
411 411
412 412 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
413 413 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
414 414 hgsettings3 = RhodeCodeSetting('ga_code', '')
415 415
416 416 self.sa.add(web1)
417 417 self.sa.add(web2)
418 418 self.sa.add(web3)
419 419 self.sa.add(web4)
420 420 self.sa.add(paths)
421 421 self.sa.add(hgsettings1)
422 422 self.sa.add(hgsettings2)
423 423 self.sa.add(hgsettings3)
424 424
425 425 self.create_ldap_options()
426 426
427 427 log.info('created ui config')
428 428
429 429 def create_user(self, username, password, email='', admin=False):
430 430 log.info('creating user %s' % username)
431 431 UserModel().create_or_update(username, password, email,
432 432 name='RhodeCode', lastname='Admin',
433 433 active=True, admin=admin)
434 434
435 435 def create_default_user(self):
436 436 log.info('creating default user')
437 437 # create default user for handling default permissions.
438 438 UserModel().create_or_update(username='default',
439 439 password=str(uuid.uuid1())[:8],
440 440 email='anonymous@rhodecode.org',
441 441 name='Anonymous', lastname='User')
442 442
443 443 def create_permissions(self):
444 444 # module.(access|create|change|delete)_[name]
445 # module.(read|write|owner)
446 perms = [('repository.none', 'Repository no access'),
447 ('repository.read', 'Repository read access'),
448 ('repository.write', 'Repository write access'),
449 ('repository.admin', 'Repository admin access'),
450 ('hg.admin', 'Hg Administrator'),
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'),
445 # module.(none|read|write|admin)
446 perms = [
447 ('repository.none', 'Repository no access'),
448 ('repository.read', 'Repository read access'),
449 ('repository.write', 'Repository write access'),
450 ('repository.admin', 'Repository admin access'),
457 451
458 ('hg.register.auto_activate', 'Register new user with '
459 'RhodeCode without auto '
460 'activation'),
461 ]
452 ('group.none', 'Repositories Group no access'),
453 ('group.read', 'Repositories Group read access'),
454 ('group.write', 'Repositories Group write access'),
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 468 for p in perms:
464 469 new_perm = Permission()
465 470 new_perm.permission_name = p[0]
466 471 new_perm.permission_longname = p[1]
467 472 self.sa.add(new_perm)
468 473
469 474 def populate_default_permissions(self):
470 475 log.info('creating default user permissions')
471 476
472 477 default_user = self.sa.query(User)\
473 478 .filter(User.username == 'default').scalar()
474 479
475 480 reg_perm = UserToPerm()
476 481 reg_perm.user = default_user
477 482 reg_perm.permission = self.sa.query(Permission)\
478 483 .filter(Permission.permission_name == 'hg.register.manual_activate')\
479 484 .scalar()
480 485
481 486 create_repo_perm = UserToPerm()
482 487 create_repo_perm.user = default_user
483 488 create_repo_perm.permission = self.sa.query(Permission)\
484 489 .filter(Permission.permission_name == 'hg.create.repository')\
485 490 .scalar()
486 491
487 492 default_repo_perm = UserToPerm()
488 493 default_repo_perm.user = default_user
489 494 default_repo_perm.permission = self.sa.query(Permission)\
490 495 .filter(Permission.permission_name == 'repository.read')\
491 496 .scalar()
492 497
493 498 self.sa.add(reg_perm)
494 499 self.sa.add(create_repo_perm)
495 500 self.sa.add(default_repo_perm)
@@ -1,155 +1,155 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.hooks
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Hooks runned by rhodecode
7 7
8 8 :created_on: Aug 6, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import sys
27 27
28 28 from mercurial.scmutil import revrange
29 29 from mercurial.node import nullrev
30 30
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib.utils import action_logger
33 33
34 34
35 35 def repo_size(ui, repo, hooktype=None, **kwargs):
36 36 """
37 37 Presents size of repository after push
38 38
39 39 :param ui:
40 40 :param repo:
41 41 :param hooktype:
42 42 """
43 43
44 44 size_hg, size_root = 0, 0
45 45 for path, dirs, files in os.walk(repo.root):
46 46 if path.find('.hg') != -1:
47 47 for f in files:
48 48 try:
49 49 size_hg += os.path.getsize(os.path.join(path, f))
50 50 except OSError:
51 51 pass
52 52 else:
53 53 for f in files:
54 54 try:
55 55 size_root += os.path.getsize(os.path.join(path, f))
56 56 except OSError:
57 57 pass
58 58
59 59 size_hg_f = h.format_byte_size(size_hg)
60 60 size_root_f = h.format_byte_size(size_root)
61 61 size_total_f = h.format_byte_size(size_root + size_hg)
62 62
63 63 last_cs = repo[len(repo) - 1]
64 64
65 65 msg = ('Repository size .hg:%s repo:%s total:%s\n'
66 66 'Last revision is now r%s:%s\n') % (
67 67 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
68 68 )
69 69
70 70 sys.stdout.write(msg)
71 71
72 72
73 73 def log_pull_action(ui, repo, **kwargs):
74 74 """
75 75 Logs user last pull action
76 76
77 77 :param ui:
78 78 :param repo:
79 79 """
80 80
81 81 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
82 82 username = extra_params['username']
83 83 repository = extra_params['repository']
84 84 action = 'pull'
85 85
86 86 action_logger(username, action, repository, extra_params['ip'],
87 87 commit=True)
88 88
89 89 return 0
90 90
91 91
92 92 def log_push_action(ui, repo, **kwargs):
93 93 """
94 94 Maps user last push action to new changeset id, from mercurial
95 95
96 96 :param ui:
97 97 :param repo:
98 98 """
99 99
100 100 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
101 101 username = extra_params['username']
102 102 repository = extra_params['repository']
103 103 action = extra_params['action'] + ':%s'
104 104 node = kwargs['node']
105 105
106 106 def get_revs(repo, rev_opt):
107 107 if rev_opt:
108 108 revs = revrange(repo, rev_opt)
109 109
110 110 if len(revs) == 0:
111 111 return (nullrev, nullrev)
112 112 return (max(revs), min(revs))
113 113 else:
114 114 return (len(repo) - 1, 0)
115 115
116 116 stop, start = get_revs(repo, [node + ':'])
117 117
118 118 revs = (str(repo[r]) for r in xrange(start, stop + 1))
119 119
120 120 action = action % ','.join(revs)
121 121
122 122 action_logger(username, action, repository, extra_params['ip'],
123 123 commit=True)
124 124
125 125 return 0
126 126
127 127
128 128 def log_create_repository(repository_dict, created_by, **kwargs):
129 129 """
130 130 Post create repository Hook. This is a dummy function for admins to re-use
131 131 if needed
132 132
133 :param repository: dict dump of repository object
133 :param repository: dict dump of repository object
134 134 :param created_by: username who created repository
135 135 :param created_date: date of creation
136 136
137 137 available keys of repository_dict:
138 138
139 139 'repo_type',
140 140 'description',
141 141 'private',
142 142 'created_on',
143 143 'enable_downloads',
144 144 'repo_id',
145 145 'user_id',
146 146 'enable_statistics',
147 147 'clone_uri',
148 148 'fork_id',
149 149 'group_id',
150 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 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import paste
31 31 import beaker
32 32 import tarfile
33 33 import shutil
34 34 from os.path import abspath
35 35 from os.path import dirname as dn, join as jn
36 36
37 37 from paste.script.command import Command, BadCommand
38 38
39 39 from mercurial import ui, config
40 40
41 41 from webhelpers.text import collapse, remove_formatting, strip_tags
42 42
43 43 from vcs import get_backend
44 44 from vcs.backends.base import BaseChangeset
45 45 from vcs.utils.lazy import LazyProperty
46 46 from vcs.utils.helpers import get_scm
47 47 from vcs.exceptions import VCSError
48 48
49 49 from rhodecode.lib.caching_query import FromCache
50 50
51 51 from rhodecode.model import meta
52 52 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 53 UserLog, RepoGroup, RhodeCodeSetting
54 54 from rhodecode.model.meta import Session
55 from rhodecode.model.repos_group import ReposGroupModel
55 56
56 57 log = logging.getLogger(__name__)
57 58
58 59
59 60 def recursive_replace(str_, replace=' '):
60 61 """Recursive replace of given sign to just one instance
61 62
62 63 :param str_: given string
63 64 :param replace: char to find and replace multiple instances
64 65
65 66 Examples::
66 67 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
67 68 'Mighty-Mighty-Bo-sstones'
68 69 """
69 70
70 71 if str_.find(replace * 2) == -1:
71 72 return str_
72 73 else:
73 74 str_ = str_.replace(replace * 2, replace)
74 75 return recursive_replace(str_, replace)
75 76
76 77
77 78 def repo_name_slug(value):
78 79 """Return slug of name of repository
79 80 This function is called on each creation/modification
80 81 of repository to prevent bad names in repo
81 82 """
82 83
83 84 slug = remove_formatting(value)
84 85 slug = strip_tags(slug)
85 86
86 87 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
87 88 slug = slug.replace(c, '-')
88 89 slug = recursive_replace(slug, '-')
89 90 slug = collapse(slug, '-')
90 91 return slug
91 92
92 93
93 94 def get_repo_slug(request):
94 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 102 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
98 103 """
99 104 Action logger for various actions made by users
100 105
101 106 :param user: user that made this action, can be a unique username string or
102 107 object containing user_id attribute
103 108 :param action: action to log, should be on of predefined unique actions for
104 109 easy translations
105 110 :param repo: string name of repository or object containing repo_id,
106 111 that action was made on
107 112 :param ipaddr: optional ip address from what the action was made
108 113 :param sa: optional sqlalchemy session
109 114
110 115 """
111 116
112 117 if not sa:
113 118 sa = meta.Session
114 119
115 120 try:
116 121 if hasattr(user, 'user_id'):
117 122 user_obj = user
118 123 elif isinstance(user, basestring):
119 124 user_obj = User.get_by_username(user)
120 125 else:
121 126 raise Exception('You have to provide user object or username')
122 127
123 128 if hasattr(repo, 'repo_id'):
124 129 repo_obj = Repository.get(repo.repo_id)
125 130 repo_name = repo_obj.repo_name
126 131 elif isinstance(repo, basestring):
127 132 repo_name = repo.lstrip('/')
128 133 repo_obj = Repository.get_by_repo_name(repo_name)
129 134 else:
130 135 raise Exception('You have to provide repository to action logger')
131 136
132 137 user_log = UserLog()
133 138 user_log.user_id = user_obj.user_id
134 139 user_log.action = action
135 140
136 141 user_log.repository_id = repo_obj.repo_id
137 142 user_log.repository_name = repo_name
138 143
139 144 user_log.action_date = datetime.datetime.now()
140 145 user_log.user_ip = ipaddr
141 146 sa.add(user_log)
142 147
143 148 log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
144 149 if commit:
145 150 sa.commit()
146 151 except:
147 152 log.error(traceback.format_exc())
148 153 raise
149 154
150 155
151 156 def get_repos(path, recursive=False):
152 157 """
153 158 Scans given path for repos and return (name,(type,path)) tuple
154 159
155 160 :param path: path to scan for repositories
156 161 :param recursive: recursive search and return names with subdirs in front
157 162 """
158 163
159 164 # remove ending slash for better results
160 165 path = path.rstrip(os.sep)
161 166
162 167 def _get_repos(p):
163 168 if not os.access(p, os.W_OK):
164 169 return
165 170 for dirpath in os.listdir(p):
166 171 if os.path.isfile(os.path.join(p, dirpath)):
167 172 continue
168 173 cur_path = os.path.join(p, dirpath)
169 174 try:
170 175 scm_info = get_scm(cur_path)
171 176 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
172 177 except VCSError:
173 178 if not recursive:
174 179 continue
175 180 #check if this dir containts other repos for recursive scan
176 181 rec_path = os.path.join(p, dirpath)
177 182 if os.path.isdir(rec_path):
178 183 for inner_scm in _get_repos(rec_path):
179 184 yield inner_scm
180 185
181 186 return _get_repos(path)
182 187
183 188
184 189 def is_valid_repo(repo_name, base_path):
185 190 """
186 191 Returns True if given path is a valid repository False otherwise
187 192 :param repo_name:
188 193 :param base_path:
189 194
190 195 :return True: if given path is a valid repository
191 196 """
192 197 full_path = os.path.join(base_path, repo_name)
193 198
194 199 try:
195 200 get_scm(full_path)
196 201 return True
197 202 except VCSError:
198 203 return False
199 204
205
200 206 def is_valid_repos_group(repos_group_name, base_path):
201 207 """
202 208 Returns True if given path is a repos group False otherwise
203 209
204 210 :param repo_name:
205 211 :param base_path:
206 212 """
207 213 full_path = os.path.join(base_path, repos_group_name)
208 214
209 215 # check if it's not a repo
210 216 if is_valid_repo(repos_group_name, base_path):
211 217 return False
212 218
213 219 # check if it's a valid path
214 220 if os.path.isdir(full_path):
215 221 return True
216 222
217 223 return False
218 224
225
219 226 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
220 227 while True:
221 228 ok = raw_input(prompt)
222 229 if ok in ('y', 'ye', 'yes'):
223 230 return True
224 231 if ok in ('n', 'no', 'nop', 'nope'):
225 232 return False
226 233 retries = retries - 1
227 234 if retries < 0:
228 235 raise IOError
229 236 print complaint
230 237
231 238 #propagated from mercurial documentation
232 239 ui_sections = ['alias', 'auth',
233 240 'decode/encode', 'defaults',
234 241 'diff', 'email',
235 242 'extensions', 'format',
236 243 'merge-patterns', 'merge-tools',
237 244 'hooks', 'http_proxy',
238 245 'smtp', 'patch',
239 246 'paths', 'profiling',
240 247 'server', 'trusted',
241 248 'ui', 'web', ]
242 249
243 250
244 251 def make_ui(read_from='file', path=None, checkpaths=True):
245 252 """A function that will read python rc files or database
246 253 and make an mercurial ui object from read options
247 254
248 255 :param path: path to mercurial config file
249 256 :param checkpaths: check the path
250 257 :param read_from: read from 'file' or 'db'
251 258 """
252 259
253 260 baseui = ui.ui()
254 261
255 262 #clean the baseui object
256 263 baseui._ocfg = config.config()
257 264 baseui._ucfg = config.config()
258 265 baseui._tcfg = config.config()
259 266
260 267 if read_from == 'file':
261 268 if not os.path.isfile(path):
262 269 log.warning('Unable to read config file %s' % path)
263 270 return False
264 271 log.debug('reading hgrc from %s' % path)
265 272 cfg = config.config()
266 273 cfg.read(path)
267 274 for section in ui_sections:
268 275 for k, v in cfg.items(section):
269 276 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
270 277 baseui.setconfig(section, k, v)
271 278
272 279 elif read_from == 'db':
273 280 sa = meta.Session
274 281 ret = sa.query(RhodeCodeUi)\
275 282 .options(FromCache("sql_cache_short",
276 283 "get_hg_ui_settings")).all()
277 284
278 285 hg_ui = ret
279 286 for ui_ in hg_ui:
280 287 if ui_.ui_active:
281 288 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
282 289 ui_.ui_key, ui_.ui_value)
283 290 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
284 291
285 292 meta.Session.remove()
286 293 return baseui
287 294
288 295
289 296 def set_rhodecode_config(config):
290 297 """
291 298 Updates pylons config with new settings from database
292 299
293 300 :param config:
294 301 """
295 302 hgsettings = RhodeCodeSetting.get_app_settings()
296 303
297 304 for k, v in hgsettings.items():
298 305 config[k] = v
299 306
300 307
301 308 def invalidate_cache(cache_key, *args):
302 309 """
303 310 Puts cache invalidation task into db for
304 311 further global cache invalidation
305 312 """
306 313
307 314 from rhodecode.model.scm import ScmModel
308 315
309 316 if cache_key.startswith('get_repo_cached_'):
310 317 name = cache_key.split('get_repo_cached_')[-1]
311 318 ScmModel().mark_for_invalidation(name)
312 319
313 320
314 321 class EmptyChangeset(BaseChangeset):
315 322 """
316 323 An dummy empty changeset. It's possible to pass hash when creating
317 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 329 self._empty_cs = cs
322 330 self.revision = -1
323 331 self.message = ''
324 332 self.author = ''
325 333 self.date = ''
326 334 self.repository = repo
327 335 self.requested_revision = requested_revision
328 336 self.alias = alias
329 337
330 338 @LazyProperty
331 339 def raw_id(self):
332 340 """
333 341 Returns raw string identifying this changeset, useful for web
334 342 representation.
335 343 """
336 344
337 345 return self._empty_cs
338 346
339 347 @LazyProperty
340 348 def branch(self):
341 349 return get_backend(self.alias).DEFAULT_BRANCH_NAME
342 350
343 351 @LazyProperty
344 352 def short_id(self):
345 353 return self.raw_id[:12]
346 354
347 355 def get_file_changeset(self, path):
348 356 return self
349 357
350 358 def get_file_content(self, path):
351 359 return u''
352 360
353 361 def get_file_size(self, path):
354 362 return 0
355 363
356 364
357 365 def map_groups(groups):
358 366 """
359 367 Checks for groups existence, and creates groups structures.
360 368 It returns last group in structure
361 369
362 370 :param groups: list of groups structure
363 371 """
364 372 sa = meta.Session
365 373
366 374 parent = None
367 375 group = None
368 376
369 377 # last element is repo in nested groups structure
370 378 groups = groups[:-1]
371
379 rgm = ReposGroupModel(sa)
372 380 for lvl, group_name in enumerate(groups):
381 log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
373 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 391 if group is None:
377 392 group = RepoGroup(group_name, parent)
393 group.group_description = desc
378 394 sa.add(group)
395 rgm._create_default_perms(group)
379 396 sa.commit()
380 397 parent = group
381 398 return group
382 399
383 400
384 401 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
385 402 """
386 403 maps all repos given in initial_repo_list, non existing repositories
387 404 are created, if remove_obsolete is True it also check for db entries
388 405 that are not in initial_repo_list and removes them.
389 406
390 407 :param initial_repo_list: list of repositories found by scanning methods
391 408 :param remove_obsolete: check for obsolete entries in database
392 409 """
393 410 from rhodecode.model.repo import RepoModel
394 411 sa = meta.Session
395 412 rm = RepoModel()
396 413 user = sa.query(User).filter(User.admin == True).first()
397 414 if user is None:
398 415 raise Exception('Missing administrative account !')
399 416 added = []
400 417
401 418 for name, repo in initial_repo_list.items():
402 419 group = map_groups(name.split(Repository.url_sep()))
403 420 if not rm.get_by_repo_name(name, cache=False):
404 421 log.info('repository %s not found creating default' % name)
405 422 added.append(name)
406 423 form_data = {
407 'repo_name': name,
408 'repo_name_full': name,
409 'repo_type': repo.alias,
410 'description': repo.description \
411 if repo.description != 'unknown' else \
412 '%s repository' % name,
413 'private': False,
414 'group_id': getattr(group, 'group_id', None)
415 }
424 'repo_name': name,
425 'repo_name_full': name,
426 'repo_type': repo.alias,
427 'description': repo.description \
428 if repo.description != 'unknown' else '%s repository' % name,
429 'private': False,
430 'group_id': getattr(group, 'group_id', None)
431 }
416 432 rm.create(form_data, user, just_db=True)
417 433 sa.commit()
418 434 removed = []
419 435 if remove_obsolete:
420 436 #remove from database those repositories that are not in the filesystem
421 437 for repo in sa.query(Repository).all():
422 438 if repo.repo_name not in initial_repo_list.keys():
423 439 removed.append(repo.repo_name)
424 440 sa.delete(repo)
425 441 sa.commit()
426 442
427 443 return added, removed
428 444
445
429 446 # set cache regions for beaker so celery can utilise it
430 447 def add_cache(settings):
431 448 cache_settings = {'regions': None}
432 449 for key in settings.keys():
433 450 for prefix in ['beaker.cache.', 'cache.']:
434 451 if key.startswith(prefix):
435 452 name = key.split(prefix)[1].strip()
436 453 cache_settings[name] = settings[key].strip()
437 454 if cache_settings['regions']:
438 455 for region in cache_settings['regions'].split(','):
439 456 region = region.strip()
440 457 region_settings = {}
441 458 for key, value in cache_settings.items():
442 459 if key.startswith(region):
443 460 region_settings[key.split('.')[1]] = value
444 461 region_settings['expire'] = int(region_settings.get('expire',
445 462 60))
446 463 region_settings.setdefault('lock_dir',
447 464 cache_settings.get('lock_dir'))
448 465 region_settings.setdefault('data_dir',
449 466 cache_settings.get('data_dir'))
450 467
451 468 if 'type' not in region_settings:
452 469 region_settings['type'] = cache_settings.get('type',
453 470 'memory')
454 471 beaker.cache.cache_regions[region] = region_settings
455 472
456 473
457 474 #==============================================================================
458 475 # TEST FUNCTIONS AND CREATORS
459 476 #==============================================================================
460 477 def create_test_index(repo_location, config, full_index):
461 478 """
462 479 Makes default test index
463 480
464 481 :param config: test config
465 482 :param full_index:
466 483 """
467 484
468 485 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
469 486 from rhodecode.lib.pidlock import DaemonLock, LockHeld
470 487
471 488 repo_location = repo_location
472 489
473 490 index_location = os.path.join(config['app_conf']['index_dir'])
474 491 if not os.path.exists(index_location):
475 492 os.makedirs(index_location)
476 493
477 494 try:
478 495 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
479 496 WhooshIndexingDaemon(index_location=index_location,
480 497 repo_location=repo_location)\
481 498 .run(full_index=full_index)
482 499 l.release()
483 500 except LockHeld:
484 501 pass
485 502
486 503
487 504 def create_test_env(repos_test_path, config):
488 505 """
489 506 Makes a fresh database and
490 507 install test repository into tmp dir
491 508 """
492 509 from rhodecode.lib.db_manage import DbManage
493 510 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
494 511
495 512 # PART ONE create db
496 513 dbconf = config['sqlalchemy.db1.url']
497 514 log.debug('making test db %s' % dbconf)
498 515
499 516 # create test dir if it doesn't exist
500 517 if not os.path.isdir(repos_test_path):
501 518 log.debug('Creating testdir %s' % repos_test_path)
502 519 os.makedirs(repos_test_path)
503 520
504 521 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
505 522 tests=True)
506 523 dbmanage.create_tables(override=True)
507 524 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
508 525 dbmanage.create_default_user()
509 526 dbmanage.admin_prompt()
510 527 dbmanage.create_permissions()
511 528 dbmanage.populate_default_permissions()
512 529 Session.commit()
513 530 # PART TWO make test repo
514 531 log.debug('making test vcs repositories')
515 532
516 533 idx_path = config['app_conf']['index_dir']
517 534 data_path = config['app_conf']['cache_dir']
518 535
519 536 #clean index and data
520 537 if idx_path and os.path.exists(idx_path):
521 538 log.debug('remove %s' % idx_path)
522 539 shutil.rmtree(idx_path)
523 540
524 541 if data_path and os.path.exists(data_path):
525 542 log.debug('remove %s' % data_path)
526 543 shutil.rmtree(data_path)
527 544
528 545 #CREATE DEFAULT HG REPOSITORY
529 546 cur_dir = dn(dn(abspath(__file__)))
530 547 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
531 548 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
532 549 tar.close()
533 550
534 551
535 552 #==============================================================================
536 553 # PASTER COMMANDS
537 554 #==============================================================================
538 555 class BasePasterCommand(Command):
539 556 """
540 557 Abstract Base Class for paster commands.
541 558
542 559 The celery commands are somewhat aggressive about loading
543 560 celery.conf, and since our module sets the `CELERY_LOADER`
544 561 environment variable to our loader, we have to bootstrap a bit and
545 562 make sure we've had a chance to load the pylons config off of the
546 563 command line, otherwise everything fails.
547 564 """
548 565 min_args = 1
549 566 min_args_error = "Please provide a paster config file as an argument."
550 567 takes_config_file = 1
551 568 requires_config_file = True
552 569
553 570 def notify_msg(self, msg, log=False):
554 571 """Make a notification to user, additionally if logger is passed
555 572 it logs this action using given logger
556 573
557 574 :param msg: message that will be printed to user
558 575 :param log: logging instance, to use to additionally log this message
559 576
560 577 """
561 578 if log and isinstance(log, logging):
562 579 log(msg)
563 580
564 581 def run(self, args):
565 582 """
566 583 Overrides Command.run
567 584
568 585 Checks for a config file argument and loads it.
569 586 """
570 587 if len(args) < self.min_args:
571 588 raise BadCommand(
572 589 self.min_args_error % {'min_args': self.min_args,
573 590 'actual_args': len(args)})
574 591
575 592 # Decrement because we're going to lob off the first argument.
576 593 # @@ This is hacky
577 594 self.min_args -= 1
578 595 self.bootstrap_config(args[0])
579 596 self.update_parser()
580 597 return super(BasePasterCommand, self).run(args[1:])
581 598
582 599 def update_parser(self):
583 600 """
584 601 Abstract method. Allows for the class's parser to be updated
585 602 before the superclass's `run` method is called. Necessary to
586 603 allow options/arguments to be passed through to the underlying
587 604 celery command.
588 605 """
589 606 raise NotImplementedError("Abstract Method.")
590 607
591 608 def bootstrap_config(self, conf):
592 609 """
593 610 Loads the pylons configuration.
594 611 """
595 612 from pylons import config as pylonsconfig
596 613
597 614 path_to_ini_file = os.path.realpath(conf)
598 615 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
599 616 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,92 +1,98 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 The application's model objects
7 7
8 8 :created_on: Nov 25, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12
13 13
14 14 :example:
15 15
16 16 .. code-block:: python
17 17
18 18 from paste.deploy import appconfig
19 19 from pylons import config
20 20 from sqlalchemy import engine_from_config
21 21 from rhodecode.config.environment import load_environment
22 22
23 23 conf = appconfig('config:development.ini', relative_to = './../../')
24 24 load_environment(conf.global_conf, conf.local_conf)
25 25
26 26 engine = engine_from_config(config, 'sqlalchemy.')
27 27 init_model(engine)
28 28 # RUN YOUR CODE HERE
29 29
30 30 """
31 31 # This program is free software: you can redistribute it and/or modify
32 32 # it under the terms of the GNU General Public License as published by
33 33 # the Free Software Foundation, either version 3 of the License, or
34 34 # (at your option) any later version.
35 35 #
36 36 # This program is distributed in the hope that it will be useful,
37 37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
38 38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 39 # GNU General Public License for more details.
40 40 #
41 41 # You should have received a copy of the GNU General Public License
42 42 # along with this program. If not, see <http://www.gnu.org/licenses/>.
43 43
44 44 import logging
45 45
46 46 from rhodecode.model import meta
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 def init_model(engine):
52 52 """
53 53 Initializes db session, bind the engine with the metadata,
54 54 Call this before using any of the tables or classes in the model,
55 55 preferably once in application start
56 56
57 57 :param engine: engine to bind to
58 58 """
59 59 log.info("initializing db for %s" % engine)
60 60 meta.Base.metadata.bind = engine
61 61
62 62
63 63 class BaseModel(object):
64 64 """
65 65 Base Model for all RhodeCode models, it adds sql alchemy session
66 66 into instance of model
67 67
68 68 :param sa: If passed it reuses this session instead of creating a new one
69 69 """
70 70
71 71 def __init__(self, sa=None):
72 72 if sa is not None:
73 73 self.sa = sa
74 74 else:
75 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 81 :param cls: class to fetch
82 82 :param instance: int or Instance
83 :param callback: callback to call if all lookups failed
83 84 """
84 85
85 86 if isinstance(instance, cls):
86 87 return instance
87 88 elif isinstance(instance, int) or str(instance).isdigit():
88 89 return cls.get(instance)
89 90 else:
90 91 if instance:
91 raise Exception('given object must be int or Instance'
92 ' of %s got %s' % (type(cls), type(instance)))
92 if callback is None:
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 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 from collections import defaultdict
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.ext.hybrid import hybrid_property
34 34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 35 from beaker.cache import cache_region, region_invalidate
36 36
37 37 from vcs import get_backend
38 38 from vcs.utils.helpers import get_scm
39 39 from vcs.exceptions import VCSError
40 40 from vcs.utils.lazy import LazyProperty
41 41
42 42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
43 43 from rhodecode.lib.exceptions import UsersGroupsAssignedException
44 44 from rhodecode.lib.compat import json
45 45 from rhodecode.lib.caching_query import FromCache
46 46
47 47 from rhodecode.model.meta import Base, Session
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51 #==============================================================================
52 52 # BASE CLASSES
53 53 #==============================================================================
54 54
55 55
56 56 class ModelSerializer(json.JSONEncoder):
57 57 """
58 58 Simple Serializer for JSON,
59 59
60 60 usage::
61 61
62 62 to make object customized for serialization implement a __json__
63 63 method that will return a dict for serialization into json
64 64
65 65 example::
66 66
67 67 class Task(object):
68 68
69 69 def __init__(self, name, value):
70 70 self.name = name
71 71 self.value = value
72 72
73 73 def __json__(self):
74 74 return dict(name=self.name,
75 75 value=self.value)
76 76
77 77 """
78 78
79 79 def default(self, obj):
80 80
81 81 if hasattr(obj, '__json__'):
82 82 return obj.__json__()
83 83 else:
84 84 return json.JSONEncoder.default(self, obj)
85 85
86 86
87 87 class BaseModel(object):
88 88 """
89 89 Base Model for all classess
90 90 """
91 91
92 92 @classmethod
93 93 def _get_keys(cls):
94 94 """return column names for this model """
95 95 return class_mapper(cls).c.keys()
96 96
97 97 def get_dict(self):
98 98 """
99 99 return dict with keys and values corresponding
100 100 to this model data """
101 101
102 102 d = {}
103 103 for k in self._get_keys():
104 104 d[k] = getattr(self, k)
105 105
106 106 # also use __json__() if present to get additional fields
107 107 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
108 108 d[k] = val
109 109 return d
110 110
111 111 def get_appstruct(self):
112 112 """return list with keys and values tupples corresponding
113 113 to this model data """
114 114
115 115 l = []
116 116 for k in self._get_keys():
117 117 l.append((k, getattr(self, k),))
118 118 return l
119 119
120 120 def populate_obj(self, populate_dict):
121 121 """populate model with data from given populate_dict"""
122 122
123 123 for k in self._get_keys():
124 124 if k in populate_dict:
125 125 setattr(self, k, populate_dict[k])
126 126
127 127 @classmethod
128 128 def query(cls):
129 129 return Session.query(cls)
130 130
131 131 @classmethod
132 132 def get(cls, id_):
133 133 if id_:
134 134 return cls.query().get(id_)
135 135
136 136 @classmethod
137 137 def getAll(cls):
138 138 return cls.query().all()
139 139
140 140 @classmethod
141 141 def delete(cls, id_):
142 142 obj = cls.query().get(id_)
143 143 Session.delete(obj)
144 144
145 145
146 146 class RhodeCodeSetting(Base, BaseModel):
147 147 __tablename__ = 'rhodecode_settings'
148 148 __table_args__ = (
149 149 UniqueConstraint('app_settings_name'),
150 150 {'extend_existing': True}
151 151 )
152 152 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
153 153 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
154 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 156 def __init__(self, k='', v=''):
157 157 self.app_settings_name = k
158 158 self.app_settings_value = v
159 159
160 160 @validates('_app_settings_value')
161 161 def validate_settings_value(self, key, val):
162 162 assert type(val) == unicode
163 163 return val
164 164
165 165 @hybrid_property
166 166 def app_settings_value(self):
167 167 v = self._app_settings_value
168 168 if v == 'ldap_active':
169 169 v = str2bool(v)
170 170 return v
171 171
172 172 @app_settings_value.setter
173 173 def app_settings_value(self, val):
174 174 """
175 175 Setter that will always make sure we use unicode in app_settings_value
176 176
177 177 :param val:
178 178 """
179 179 self._app_settings_value = safe_unicode(val)
180 180
181 181 def __repr__(self):
182 182 return "<%s('%s:%s')>" % (
183 183 self.__class__.__name__,
184 184 self.app_settings_name, self.app_settings_value
185 185 )
186 186
187 187 @classmethod
188 188 def get_by_name(cls, ldap_key):
189 189 return cls.query()\
190 190 .filter(cls.app_settings_name == ldap_key).scalar()
191 191
192 192 @classmethod
193 193 def get_app_settings(cls, cache=False):
194 194
195 195 ret = cls.query()
196 196
197 197 if cache:
198 198 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
199 199
200 200 if not ret:
201 201 raise Exception('Could not get application settings !')
202 202 settings = {}
203 203 for each in ret:
204 204 settings['rhodecode_' + each.app_settings_name] = \
205 205 each.app_settings_value
206 206
207 207 return settings
208 208
209 209 @classmethod
210 210 def get_ldap_settings(cls, cache=False):
211 211 ret = cls.query()\
212 212 .filter(cls.app_settings_name.startswith('ldap_')).all()
213 213 fd = {}
214 214 for row in ret:
215 215 fd.update({row.app_settings_name:row.app_settings_value})
216 216
217 217 return fd
218 218
219 219
220 220 class RhodeCodeUi(Base, BaseModel):
221 221 __tablename__ = 'rhodecode_ui'
222 222 __table_args__ = (
223 223 UniqueConstraint('ui_key'),
224 224 {'extend_existing': True}
225 225 )
226 226
227 227 HOOK_UPDATE = 'changegroup.update'
228 228 HOOK_REPO_SIZE = 'changegroup.repo_size'
229 229 HOOK_PUSH = 'pretxnchangegroup.push_logger'
230 230 HOOK_PULL = 'preoutgoing.pull_logger'
231 231
232 232 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
233 233 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
234 234 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
235 235 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
236 236 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
237 237
238 238 @classmethod
239 239 def get_by_key(cls, key):
240 240 return cls.query().filter(cls.ui_key == key)
241 241
242 242 @classmethod
243 243 def get_builtin_hooks(cls):
244 244 q = cls.query()
245 245 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
246 246 cls.HOOK_REPO_SIZE,
247 247 cls.HOOK_PUSH, cls.HOOK_PULL]))
248 248 return q.all()
249 249
250 250 @classmethod
251 251 def get_custom_hooks(cls):
252 252 q = cls.query()
253 253 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
254 254 cls.HOOK_REPO_SIZE,
255 255 cls.HOOK_PUSH, cls.HOOK_PULL]))
256 256 q = q.filter(cls.ui_section == 'hooks')
257 257 return q.all()
258 258
259 259 @classmethod
260 260 def create_or_update_hook(cls, key, val):
261 261 new_ui = cls.get_by_key(key).scalar() or cls()
262 262 new_ui.ui_section = 'hooks'
263 263 new_ui.ui_active = True
264 264 new_ui.ui_key = key
265 265 new_ui.ui_value = val
266 266
267 267 Session.add(new_ui)
268 268
269 269
270 270 class User(Base, BaseModel):
271 271 __tablename__ = 'users'
272 272 __table_args__ = (
273 273 UniqueConstraint('username'), UniqueConstraint('email'),
274 274 {'extend_existing': True}
275 275 )
276 276 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 277 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
278 278 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
279 279 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
280 280 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
281 281 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
282 282 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
283 283 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
284 284 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
285 285 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 286 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
287 287
288 288 user_log = relationship('UserLog', cascade='all')
289 289 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
290 290
291 291 repositories = relationship('Repository')
292 292 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
293 293 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
294 294
295 295 group_member = relationship('UsersGroupMember', cascade='all')
296 296
297 297 notifications = relationship('UserNotification',)
298 298
299 299 @hybrid_property
300 300 def email(self):
301 301 return self._email
302 302
303 303 @email.setter
304 304 def email(self, val):
305 305 self._email = val.lower() if val else None
306 306
307 307 @property
308 308 def full_name(self):
309 309 return '%s %s' % (self.name, self.lastname)
310 310
311 311 @property
312 312 def full_name_or_username(self):
313 313 return ('%s %s' % (self.name, self.lastname)
314 314 if (self.name and self.lastname) else self.username)
315 315
316 316 @property
317 317 def full_contact(self):
318 318 return '%s %s <%s>' % (self.name, self.lastname, self.email)
319 319
320 320 @property
321 321 def short_contact(self):
322 322 return '%s %s' % (self.name, self.lastname)
323 323
324 324 @property
325 325 def is_admin(self):
326 326 return self.admin
327 327
328 328 def __repr__(self):
329 329 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
330 330 self.user_id, self.username)
331 331
332 332 @classmethod
333 333 def get_by_username(cls, username, case_insensitive=False, cache=False):
334 334 if case_insensitive:
335 335 q = cls.query().filter(cls.username.ilike(username))
336 336 else:
337 337 q = cls.query().filter(cls.username == username)
338 338
339 339 if cache:
340 340 q = q.options(FromCache("sql_cache_short",
341 341 "get_user_%s" % username))
342 342 return q.scalar()
343 343
344 344 @classmethod
345 345 def get_by_api_key(cls, api_key, cache=False):
346 346 q = cls.query().filter(cls.api_key == api_key)
347 347
348 348 if cache:
349 349 q = q.options(FromCache("sql_cache_short",
350 350 "get_api_key_%s" % api_key))
351 351 return q.scalar()
352 352
353 353 @classmethod
354 354 def get_by_email(cls, email, case_insensitive=False, cache=False):
355 355 if case_insensitive:
356 356 q = cls.query().filter(cls.email.ilike(email))
357 357 else:
358 358 q = cls.query().filter(cls.email == email)
359 359
360 360 if cache:
361 361 q = q.options(FromCache("sql_cache_short",
362 362 "get_api_key_%s" % email))
363 363 return q.scalar()
364 364
365 365 def update_lastlogin(self):
366 366 """Update user lastlogin"""
367 367 self.last_login = datetime.datetime.now()
368 368 Session.add(self)
369 369 log.debug('updated user %s lastlogin' % self.username)
370 370
371 371 def __json__(self):
372 372 return dict(
373 373 email=self.email,
374 374 full_name=self.full_name,
375 375 full_name_or_username=self.full_name_or_username,
376 376 short_contact=self.short_contact,
377 377 full_contact=self.full_contact
378 378 )
379 379
380 380
381 381 class UserLog(Base, BaseModel):
382 382 __tablename__ = 'user_logs'
383 383 __table_args__ = {'extend_existing': True}
384 384 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
385 385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
386 386 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
387 387 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
388 388 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
389 389 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
390 390 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
391 391
392 392 @property
393 393 def action_as_day(self):
394 394 return datetime.date(*self.action_date.timetuple()[:3])
395 395
396 396 user = relationship('User')
397 397 repository = relationship('Repository',cascade='')
398 398
399 399
400 400 class UsersGroup(Base, BaseModel):
401 401 __tablename__ = 'users_groups'
402 402 __table_args__ = {'extend_existing': True}
403 403
404 404 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
405 405 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
406 406 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
407 407
408 408 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
409 409
410 410 def __repr__(self):
411 411 return '<userGroup(%s)>' % (self.users_group_name)
412 412
413 413 @classmethod
414 414 def get_by_group_name(cls, group_name, cache=False,
415 415 case_insensitive=False):
416 416 if case_insensitive:
417 417 q = cls.query().filter(cls.users_group_name.ilike(group_name))
418 418 else:
419 419 q = cls.query().filter(cls.users_group_name == group_name)
420 420 if cache:
421 421 q = q.options(FromCache("sql_cache_short",
422 422 "get_user_%s" % group_name))
423 423 return q.scalar()
424 424
425 425 @classmethod
426 426 def get(cls, users_group_id, cache=False):
427 427 users_group = cls.query()
428 428 if cache:
429 429 users_group = users_group.options(FromCache("sql_cache_short",
430 430 "get_users_group_%s" % users_group_id))
431 431 return users_group.get(users_group_id)
432 432
433 433
434 434 class UsersGroupMember(Base, BaseModel):
435 435 __tablename__ = 'users_groups_members'
436 436 __table_args__ = {'extend_existing': True}
437 437
438 438 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
439 439 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
440 440 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
441 441
442 442 user = relationship('User', lazy='joined')
443 443 users_group = relationship('UsersGroup')
444 444
445 445 def __init__(self, gr_id='', u_id=''):
446 446 self.users_group_id = gr_id
447 447 self.user_id = u_id
448 448
449 449 @staticmethod
450 450 def add_user_to_group(group, user):
451 451 ugm = UsersGroupMember()
452 452 ugm.users_group = group
453 453 ugm.user = user
454 454 Session.add(ugm)
455 455 Session.commit()
456 456 return ugm
457 457
458 458
459 459 class Repository(Base, BaseModel):
460 460 __tablename__ = 'repositories'
461 461 __table_args__ = (
462 462 UniqueConstraint('repo_name'),
463 463 {'extend_existing': True},
464 464 )
465 465
466 466 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
467 467 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
468 468 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
469 469 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
470 470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
471 471 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
472 472 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
473 473 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
474 474 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
475 475 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
476 476
477 477 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
478 478 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
479 479
480 480 user = relationship('User')
481 481 fork = relationship('Repository', remote_side=repo_id)
482 482 group = relationship('RepoGroup')
483 483 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
484 484 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
485 485 stats = relationship('Statistics', cascade='all', uselist=False)
486 486
487 487 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
488 488
489 489 logs = relationship('UserLog')
490 490
491 491 def __repr__(self):
492 492 return "<%s('%s:%s')>" % (self.__class__.__name__,
493 493 self.repo_id, self.repo_name)
494 494
495 495 @classmethod
496 496 def url_sep(cls):
497 497 return '/'
498 498
499 499 @classmethod
500 500 def get_by_repo_name(cls, repo_name):
501 501 q = Session.query(cls).filter(cls.repo_name == repo_name)
502 502 q = q.options(joinedload(Repository.fork))\
503 503 .options(joinedload(Repository.user))\
504 504 .options(joinedload(Repository.group))
505 505 return q.scalar()
506 506
507 507 @classmethod
508 508 def get_repo_forks(cls, repo_id):
509 509 return cls.query().filter(Repository.fork_id == repo_id)
510 510
511 511 @classmethod
512 512 def base_path(cls):
513 513 """
514 514 Returns base path when all repos are stored
515 515
516 516 :param cls:
517 517 """
518 518 q = Session.query(RhodeCodeUi)\
519 519 .filter(RhodeCodeUi.ui_key == cls.url_sep())
520 520 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
521 521 return q.one().ui_value
522 522
523 523 @property
524 524 def just_name(self):
525 525 return self.repo_name.split(Repository.url_sep())[-1]
526 526
527 527 @property
528 528 def groups_with_parents(self):
529 529 groups = []
530 530 if self.group is None:
531 531 return groups
532 532
533 533 cur_gr = self.group
534 534 groups.insert(0, cur_gr)
535 535 while 1:
536 536 gr = getattr(cur_gr, 'parent_group', None)
537 537 cur_gr = cur_gr.parent_group
538 538 if gr is None:
539 539 break
540 540 groups.insert(0, gr)
541 541
542 542 return groups
543 543
544 544 @property
545 545 def groups_and_repo(self):
546 546 return self.groups_with_parents, self.just_name
547 547
548 548 @LazyProperty
549 549 def repo_path(self):
550 550 """
551 551 Returns base full path for that repository means where it actually
552 552 exists on a filesystem
553 553 """
554 554 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
555 555 Repository.url_sep())
556 556 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
557 557 return q.one().ui_value
558 558
559 559 @property
560 560 def repo_full_path(self):
561 561 p = [self.repo_path]
562 562 # we need to split the name by / since this is how we store the
563 563 # names in the database, but that eventually needs to be converted
564 564 # into a valid system path
565 565 p += self.repo_name.split(Repository.url_sep())
566 566 return os.path.join(*p)
567 567
568 568 def get_new_name(self, repo_name):
569 569 """
570 570 returns new full repository name based on assigned group and new new
571 571
572 572 :param group_name:
573 573 """
574 574 path_prefix = self.group.full_path_splitted if self.group else []
575 575 return Repository.url_sep().join(path_prefix + [repo_name])
576 576
577 577 @property
578 578 def _ui(self):
579 579 """
580 580 Creates an db based ui object for this repository
581 581 """
582 582 from mercurial import ui
583 583 from mercurial import config
584 584 baseui = ui.ui()
585 585
586 586 #clean the baseui object
587 587 baseui._ocfg = config.config()
588 588 baseui._ucfg = config.config()
589 589 baseui._tcfg = config.config()
590 590
591 591 ret = RhodeCodeUi.query()\
592 592 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
593 593
594 594 hg_ui = ret
595 595 for ui_ in hg_ui:
596 596 if ui_.ui_active:
597 597 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
598 598 ui_.ui_key, ui_.ui_value)
599 599 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
600 600
601 601 return baseui
602 602
603 603 @classmethod
604 604 def is_valid(cls, repo_name):
605 605 """
606 606 returns True if given repo name is a valid filesystem repository
607 607
608 608 :param cls:
609 609 :param repo_name:
610 610 """
611 611 from rhodecode.lib.utils import is_valid_repo
612 612
613 613 return is_valid_repo(repo_name, cls.base_path())
614 614
615 615 #==========================================================================
616 616 # SCM PROPERTIES
617 617 #==========================================================================
618 618
619 619 def get_changeset(self, rev):
620 620 return get_changeset_safe(self.scm_instance, rev)
621 621
622 622 @property
623 623 def tip(self):
624 624 return self.get_changeset('tip')
625 625
626 626 @property
627 627 def author(self):
628 628 return self.tip.author
629 629
630 630 @property
631 631 def last_change(self):
632 632 return self.scm_instance.last_change
633 633
634 634 def comments(self, revisions=None):
635 635 """
636 636 Returns comments for this repository grouped by revisions
637 637
638 638 :param revisions: filter query by revisions only
639 639 """
640 640 cmts = ChangesetComment.query()\
641 641 .filter(ChangesetComment.repo == self)
642 642 if revisions:
643 643 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
644 644 grouped = defaultdict(list)
645 645 for cmt in cmts.all():
646 646 grouped[cmt.revision].append(cmt)
647 647 return grouped
648 648
649 649 #==========================================================================
650 650 # SCM CACHE INSTANCE
651 651 #==========================================================================
652 652
653 653 @property
654 654 def invalidate(self):
655 655 return CacheInvalidation.invalidate(self.repo_name)
656 656
657 657 def set_invalidate(self):
658 658 """
659 659 set a cache for invalidation for this instance
660 660 """
661 661 CacheInvalidation.set_invalidate(self.repo_name)
662 662
663 663 @LazyProperty
664 664 def scm_instance(self):
665 665 return self.__get_instance()
666 666
667 667 @property
668 668 def scm_instance_cached(self):
669 669 @cache_region('long_term')
670 670 def _c(repo_name):
671 671 return self.__get_instance()
672 672 rn = self.repo_name
673 673 log.debug('Getting cached instance of repo')
674 674 inv = self.invalidate
675 675 if inv is not None:
676 676 region_invalidate(_c, None, rn)
677 677 # update our cache
678 678 CacheInvalidation.set_valid(inv.cache_key)
679 679 return _c(rn)
680 680
681 681 def __get_instance(self):
682 682 repo_full_path = self.repo_full_path
683 683 try:
684 684 alias = get_scm(repo_full_path)[0]
685 685 log.debug('Creating instance of %s repository' % alias)
686 686 backend = get_backend(alias)
687 687 except VCSError:
688 688 log.error(traceback.format_exc())
689 689 log.error('Perhaps this repository is in db and not in '
690 690 'filesystem run rescan repositories with '
691 691 '"destroy old data " option from admin panel')
692 692 return
693 693
694 694 if alias == 'hg':
695 695 repo = backend(safe_str(repo_full_path), create=False,
696 696 baseui=self._ui)
697 697 # skip hidden web repository
698 698 if repo._get_hidden():
699 699 return
700 700 else:
701 701 repo = backend(repo_full_path, create=False)
702 702
703 703 return repo
704 704
705 705
706 706 class RepoGroup(Base, BaseModel):
707 707 __tablename__ = 'groups'
708 708 __table_args__ = (
709 709 UniqueConstraint('group_name', 'group_parent_id'),
710 710 CheckConstraint('group_id != group_parent_id'),
711 711 {'extend_existing': True},
712 712 )
713 713 __mapper_args__ = {'order_by': 'group_name'}
714 714
715 715 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
716 716 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
717 717 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
718 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 723 parent_group = relationship('RepoGroup', remote_side=group_id)
721 724
722 725 def __init__(self, group_name='', parent_group=None):
723 726 self.group_name = group_name
724 727 self.parent_group = parent_group
725 728
726 729 def __repr__(self):
727 730 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
728 731 self.group_name)
729 732
730 733 @classmethod
731 734 def groups_choices(cls):
732 735 from webhelpers.html import literal as _literal
733 736 repo_groups = [('', '')]
734 737 sep = ' &raquo; '
735 738 _name = lambda k: _literal(sep.join(k))
736 739
737 740 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
738 741 for x in cls.query().all()])
739 742
740 743 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
741 744 return repo_groups
742 745
743 746 @classmethod
744 747 def url_sep(cls):
745 748 return '/'
746 749
747 750 @classmethod
748 751 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
749 752 if case_insensitive:
750 753 gr = cls.query()\
751 754 .filter(cls.group_name.ilike(group_name))
752 755 else:
753 756 gr = cls.query()\
754 757 .filter(cls.group_name == group_name)
755 758 if cache:
756 759 gr = gr.options(FromCache("sql_cache_short",
757 760 "get_group_%s" % group_name))
758 761 return gr.scalar()
759 762
760 763 @property
761 764 def parents(self):
762 765 parents_recursion_limit = 5
763 766 groups = []
764 767 if self.parent_group is None:
765 768 return groups
766 769 cur_gr = self.parent_group
767 770 groups.insert(0, cur_gr)
768 771 cnt = 0
769 772 while 1:
770 773 cnt += 1
771 774 gr = getattr(cur_gr, 'parent_group', None)
772 775 cur_gr = cur_gr.parent_group
773 776 if gr is None:
774 777 break
775 778 if cnt == parents_recursion_limit:
776 779 # this will prevent accidental infinit loops
777 780 log.error('group nested more than %s' %
778 781 parents_recursion_limit)
779 782 break
780 783
781 784 groups.insert(0, gr)
782 785 return groups
783 786
784 787 @property
785 788 def children(self):
786 789 return RepoGroup.query().filter(RepoGroup.parent_group == self)
787 790
788 791 @property
789 792 def name(self):
790 793 return self.group_name.split(RepoGroup.url_sep())[-1]
791 794
792 795 @property
793 796 def full_path(self):
794 797 return self.group_name
795 798
796 799 @property
797 800 def full_path_splitted(self):
798 801 return self.group_name.split(RepoGroup.url_sep())
799 802
800 803 @property
801 804 def repositories(self):
802 805 return Repository.query().filter(Repository.group == self)
803 806
804 807 @property
805 808 def repositories_recursive_count(self):
806 809 cnt = self.repositories.count()
807 810
808 811 def children_count(group):
809 812 cnt = 0
810 813 for child in group.children:
811 814 cnt += child.repositories.count()
812 815 cnt += children_count(child)
813 816 return cnt
814 817
815 818 return cnt + children_count(self)
816 819
817 820 def get_new_name(self, group_name):
818 821 """
819 822 returns new full group name based on parent and new name
820 823
821 824 :param group_name:
822 825 """
823 826 path_prefix = (self.parent_group.full_path_splitted if
824 827 self.parent_group else [])
825 828 return RepoGroup.url_sep().join(path_prefix + [group_name])
826 829
827 830
828 831 class Permission(Base, BaseModel):
829 832 __tablename__ = 'permissions'
830 833 __table_args__ = {'extend_existing': True}
831 834 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
832 835 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
833 836 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
834 837
835 838 def __repr__(self):
836 return "<%s('%s:%s')>" % (self.__class__.__name__,
837 self.permission_id, self.permission_name)
839 return "<%s('%s:%s')>" % (
840 self.__class__.__name__, self.permission_id, self.permission_name
841 )
838 842
839 843 @classmethod
840 844 def get_by_key(cls, key):
841 845 return cls.query().filter(cls.permission_name == key).scalar()
842 846
843 847 @classmethod
844 848 def get_default_perms(cls, default_user_id):
845 849 q = Session.query(UserRepoToPerm, Repository, cls)\
846 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
847 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
848 .filter(UserRepoToPerm.user_id == default_user_id)
850 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
851 .join((cls, UserRepoToPerm.permission_id == cls.permission_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 863 return q.all()
851 864
852 865
853 866 class UserRepoToPerm(Base, BaseModel):
854 867 __tablename__ = 'repo_to_perm'
855 868 __table_args__ = (
856 869 UniqueConstraint('user_id', 'repository_id'), {'extend_existing': True}
857 870 )
858 871 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
859 872 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
860 873 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
861 874 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
862 875
863 876 user = relationship('User')
864 877 permission = relationship('Permission')
865 878 repository = relationship('Repository')
866 879
867 880 @classmethod
868 881 def create(cls, user, repository, permission):
869 882 n = cls()
870 883 n.user = user
871 884 n.repository = repository
872 885 n.permission = permission
873 886 Session.add(n)
874 887 return n
875 888
876 889 def __repr__(self):
877 890 return '<user:%s => %s >' % (self.user, self.repository)
878 891
879 892
880 893 class UserToPerm(Base, BaseModel):
881 894 __tablename__ = 'user_to_perm'
882 895 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing': True})
883 896 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
884 897 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
885 898 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
886 899
887 900 user = relationship('User')
888 901 permission = relationship('Permission', lazy='joined')
889 902
890 903
891 904 class UsersGroupRepoToPerm(Base, BaseModel):
892 905 __tablename__ = 'users_group_repo_to_perm'
893 906 __table_args__ = (
894 907 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
895 908 {'extend_existing': True}
896 909 )
897 910 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
898 911 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
899 912 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
900 913 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
901 914
902 915 users_group = relationship('UsersGroup')
903 916 permission = relationship('Permission')
904 917 repository = relationship('Repository')
905 918
906 919 @classmethod
907 920 def create(cls, users_group, repository, permission):
908 921 n = cls()
909 922 n.users_group = users_group
910 923 n.repository = repository
911 924 n.permission = permission
912 925 Session.add(n)
913 926 return n
914 927
915 928 def __repr__(self):
916 929 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
917 930
918 931
919 932 class UsersGroupToPerm(Base, BaseModel):
920 933 __tablename__ = 'users_group_to_perm'
921 934 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
922 935 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
923 936 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
924 937
925 938 users_group = relationship('UsersGroup')
926 939 permission = relationship('Permission')
927 940
928 941
929 942 class UserRepoGroupToPerm(Base, BaseModel):
930 943 __tablename__ = 'user_repo_group_to_perm'
931 944 __table_args__ = (
932 945 UniqueConstraint('group_id', 'permission_id'),
933 946 {'extend_existing': True}
934 947 )
935 948
936 949 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
937 950 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
938 951 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
939 952 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
940 953
941 954 user = relationship('User')
942 955 permission = relationship('Permission')
943 956 group = relationship('RepoGroup')
944 957
945 958
946 959 class UsersGroupRepoGroupToPerm(Base, BaseModel):
947 960 __tablename__ = 'users_group_repo_group_to_perm'
948 961 __table_args__ = (
949 962 UniqueConstraint('group_id', 'permission_id'),
950 963 {'extend_existing': True}
951 964 )
952 965
953 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 967 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
955 968 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
956 969 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
957 970
958 971 users_group = relationship('UsersGroup')
959 972 permission = relationship('Permission')
960 973 group = relationship('RepoGroup')
961 974
962 975
963 976 class Statistics(Base, BaseModel):
964 977 __tablename__ = 'statistics'
965 978 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
966 979 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
967 980 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
968 981 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
969 982 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
970 983 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
971 984 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
972 985
973 986 repository = relationship('Repository', single_parent=True)
974 987
975 988
976 989 class UserFollowing(Base, BaseModel):
977 990 __tablename__ = 'user_followings'
978 991 __table_args__ = (
979 992 UniqueConstraint('user_id', 'follows_repository_id'),
980 993 UniqueConstraint('user_id', 'follows_user_id'),
981 994 {'extend_existing': True}
982 995 )
983 996
984 997 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
985 998 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
986 999 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
987 1000 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
988 1001 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
989 1002
990 1003 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
991 1004
992 1005 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
993 1006 follows_repository = relationship('Repository', order_by='Repository.repo_name')
994 1007
995 1008 @classmethod
996 1009 def get_repo_followers(cls, repo_id):
997 1010 return cls.query().filter(cls.follows_repo_id == repo_id)
998 1011
999 1012
1000 1013 class CacheInvalidation(Base, BaseModel):
1001 1014 __tablename__ = 'cache_invalidation'
1002 1015 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
1003 1016 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1004 1017 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1005 1018 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1006 1019 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1007 1020
1008 1021 def __init__(self, cache_key, cache_args=''):
1009 1022 self.cache_key = cache_key
1010 1023 self.cache_args = cache_args
1011 1024 self.cache_active = False
1012 1025
1013 1026 def __repr__(self):
1014 1027 return "<%s('%s:%s')>" % (self.__class__.__name__,
1015 1028 self.cache_id, self.cache_key)
1016 1029
1017 1030 @classmethod
1018 1031 def invalidate(cls, key):
1019 1032 """
1020 1033 Returns Invalidation object if this given key should be invalidated
1021 1034 None otherwise. `cache_active = False` means that this cache
1022 1035 state is not valid and needs to be invalidated
1023 1036
1024 1037 :param key:
1025 1038 """
1026 1039 return cls.query()\
1027 1040 .filter(CacheInvalidation.cache_key == key)\
1028 1041 .filter(CacheInvalidation.cache_active == False)\
1029 1042 .scalar()
1030 1043
1031 1044 @classmethod
1032 1045 def set_invalidate(cls, key):
1033 1046 """
1034 1047 Mark this Cache key for invalidation
1035 1048
1036 1049 :param key:
1037 1050 """
1038 1051
1039 1052 log.debug('marking %s for invalidation' % key)
1040 1053 inv_obj = Session.query(cls)\
1041 1054 .filter(cls.cache_key == key).scalar()
1042 1055 if inv_obj:
1043 1056 inv_obj.cache_active = False
1044 1057 else:
1045 1058 log.debug('cache key not found in invalidation db -> creating one')
1046 1059 inv_obj = CacheInvalidation(key)
1047 1060
1048 1061 try:
1049 1062 Session.add(inv_obj)
1050 1063 Session.commit()
1051 1064 except Exception:
1052 1065 log.error(traceback.format_exc())
1053 1066 Session.rollback()
1054 1067
1055 1068 @classmethod
1056 1069 def set_valid(cls, key):
1057 1070 """
1058 1071 Mark this cache key as active and currently cached
1059 1072
1060 1073 :param key:
1061 1074 """
1062 1075 inv_obj = CacheInvalidation.query()\
1063 1076 .filter(CacheInvalidation.cache_key == key).scalar()
1064 1077 inv_obj.cache_active = True
1065 1078 Session.add(inv_obj)
1066 1079 Session.commit()
1067 1080
1068 1081
1069 1082 class ChangesetComment(Base, BaseModel):
1070 1083 __tablename__ = 'changeset_comments'
1071 1084 __table_args__ = ({'extend_existing': True},)
1072 1085 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1073 1086 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1074 1087 revision = Column('revision', String(40), nullable=False)
1075 1088 line_no = Column('line_no', Unicode(10), nullable=True)
1076 1089 f_path = Column('f_path', Unicode(1000), nullable=True)
1077 1090 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1078 1091 text = Column('text', Unicode(25000), nullable=False)
1079 1092 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1080 1093
1081 1094 author = relationship('User', lazy='joined')
1082 1095 repo = relationship('Repository')
1083 1096
1084 1097 @classmethod
1085 1098 def get_users(cls, revision):
1086 1099 """
1087 1100 Returns user associated with this changesetComment. ie those
1088 1101 who actually commented
1089 1102
1090 1103 :param cls:
1091 1104 :param revision:
1092 1105 """
1093 1106 return Session.query(User)\
1094 1107 .filter(cls.revision == revision)\
1095 1108 .join(ChangesetComment.author).all()
1096 1109
1097 1110
1098 1111 class Notification(Base, BaseModel):
1099 1112 __tablename__ = 'notifications'
1100 1113 __table_args__ = ({'extend_existing': True},)
1101 1114
1102 1115 TYPE_CHANGESET_COMMENT = u'cs_comment'
1103 1116 TYPE_MESSAGE = u'message'
1104 1117 TYPE_MENTION = u'mention'
1105 1118 TYPE_REGISTRATION = u'registration'
1106 1119
1107 1120 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1108 1121 subject = Column('subject', Unicode(512), nullable=True)
1109 1122 body = Column('body', Unicode(50000), nullable=True)
1110 1123 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1111 1124 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1112 1125 type_ = Column('type', Unicode(256))
1113 1126
1114 1127 created_by_user = relationship('User')
1115 1128 notifications_to_users = relationship('UserNotification', lazy='joined',
1116 1129 cascade="all, delete, delete-orphan")
1117 1130
1118 1131 @property
1119 1132 def recipients(self):
1120 1133 return [x.user for x in UserNotification.query()\
1121 1134 .filter(UserNotification.notification == self).all()]
1122 1135
1123 1136 @classmethod
1124 1137 def create(cls, created_by, subject, body, recipients, type_=None):
1125 1138 if type_ is None:
1126 1139 type_ = Notification.TYPE_MESSAGE
1127 1140
1128 1141 notification = cls()
1129 1142 notification.created_by_user = created_by
1130 1143 notification.subject = subject
1131 1144 notification.body = body
1132 1145 notification.type_ = type_
1133 1146 notification.created_on = datetime.datetime.now()
1134 1147
1135 1148 for u in recipients:
1136 1149 assoc = UserNotification()
1137 1150 assoc.notification = notification
1138 1151 u.notifications.append(assoc)
1139 1152 Session.add(notification)
1140 1153 return notification
1141 1154
1142 1155 @property
1143 1156 def description(self):
1144 1157 from rhodecode.model.notification import NotificationModel
1145 1158 return NotificationModel().make_description(self)
1146 1159
1147 1160
1148 1161 class UserNotification(Base, BaseModel):
1149 1162 __tablename__ = 'user_to_notification'
1150 1163 __table_args__ = (
1151 1164 UniqueConstraint('user_id', 'notification_id'),
1152 1165 {'extend_existing': True}
1153 1166 )
1154 1167 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1155 1168 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1156 1169 read = Column('read', Boolean, default=False)
1157 1170 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1158 1171
1159 1172 user = relationship('User', lazy="joined")
1160 1173 notification = relationship('Notification', lazy="joined",
1161 1174 order_by=lambda: Notification.created_on.desc(),)
1162 1175
1163 1176 def mark_as_read(self):
1164 1177 self.read = True
1165 1178 Session.add(self)
1166 1179
1167 1180
1168 1181 class DbMigrateVersion(Base, BaseModel):
1169 1182 __tablename__ = 'db_migrate_version'
1170 1183 __table_args__ = {'extend_existing': True}
1171 1184 repository_id = Column('repository_id', String(250), primary_key=True)
1172 1185 repository_path = Column('repository_path', Text)
1173 1186 version = Column('version', Integer)
@@ -1,749 +1,758 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 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 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 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 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 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import os
23 23 import re
24 24 import logging
25 25 import traceback
26 26
27 27 import formencode
28 28 from formencode import All
29 29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 30 Email, Bool, StringBoolean, Set
31 31
32 32 from pylons.i18n.translation import _
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34
35 35 from rhodecode.config.routing import ADMIN_PREFIX
36 36 from rhodecode.lib.utils import repo_name_slug
37 37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 38 from rhodecode.lib.exceptions import LdapImportError
39 39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
40 40 from rhodecode import BACKENDS
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 #this is needed to translate the messages using _() in validators
46 46 class State_obj(object):
47 47 _ = staticmethod(_)
48 48
49 49
50 50 #==============================================================================
51 51 # VALIDATORS
52 52 #==============================================================================
53 53 class ValidAuthToken(formencode.validators.FancyValidator):
54 54 messages = {'invalid_token': _('Token mismatch')}
55 55
56 56 def validate_python(self, value, state):
57 57
58 58 if value != authentication_token():
59 59 raise formencode.Invalid(
60 60 self.message('invalid_token',
61 61 state, search_number=value),
62 62 value,
63 63 state
64 64 )
65 65
66 66
67 67 def ValidUsername(edit, old_data):
68 68 class _ValidUsername(formencode.validators.FancyValidator):
69 69
70 70 def validate_python(self, value, state):
71 71 if value in ['default', 'new_user']:
72 72 raise formencode.Invalid(_('Invalid username'), value, state)
73 73 #check if user is unique
74 74 old_un = None
75 75 if edit:
76 76 old_un = User.get(old_data.get('user_id')).username
77 77
78 78 if old_un != value or not edit:
79 79 if User.get_by_username(value, case_insensitive=True):
80 80 raise formencode.Invalid(_('This username already '
81 81 'exists') , value, state)
82 82
83 83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
84 84 raise formencode.Invalid(
85 85 _('Username may only contain alphanumeric characters '
86 86 'underscores, periods or dashes and must begin with '
87 87 'alphanumeric character'),
88 88 value,
89 89 state
90 90 )
91 91
92 92 return _ValidUsername
93 93
94 94
95 95 def ValidUsersGroup(edit, old_data):
96 96
97 97 class _ValidUsersGroup(formencode.validators.FancyValidator):
98 98
99 99 def validate_python(self, value, state):
100 100 if value in ['default']:
101 101 raise formencode.Invalid(_('Invalid group name'), value, state)
102 102 #check if group is unique
103 103 old_ugname = None
104 104 if edit:
105 105 old_ugname = UsersGroup.get(
106 106 old_data.get('users_group_id')).users_group_name
107 107
108 108 if old_ugname != value or not edit:
109 109 if UsersGroup.get_by_group_name(value, cache=False,
110 110 case_insensitive=True):
111 111 raise formencode.Invalid(_('This users group '
112 112 'already exists'), value,
113 113 state)
114 114
115 115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
116 116 raise formencode.Invalid(
117 117 _('RepoGroup name may only contain alphanumeric characters '
118 118 'underscores, periods or dashes and must begin with '
119 119 'alphanumeric character'),
120 120 value,
121 121 state
122 122 )
123 123
124 124 return _ValidUsersGroup
125 125
126 126
127 127 def ValidReposGroup(edit, old_data):
128 128 class _ValidReposGroup(formencode.validators.FancyValidator):
129 129
130 130 def validate_python(self, value, state):
131 131 # TODO WRITE VALIDATIONS
132 132 group_name = value.get('group_name')
133 133 group_parent_id = value.get('group_parent_id')
134 134
135 135 # slugify repo group just in case :)
136 136 slug = repo_name_slug(group_name)
137 137
138 138 # check for parent of self
139 139 parent_of_self = lambda: (
140 140 old_data['group_id'] == int(group_parent_id)
141 141 if group_parent_id else False
142 142 )
143 143 if edit and parent_of_self():
144 144 e_dict = {
145 145 'group_parent_id': _('Cannot assign this group as parent')
146 146 }
147 147 raise formencode.Invalid('', value, state,
148 148 error_dict=e_dict)
149 149
150 150 old_gname = None
151 151 if edit:
152 152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
153 153
154 154 if old_gname != group_name or not edit:
155 155
156 156 # check group
157 157 gr = RepoGroup.query()\
158 158 .filter(RepoGroup.group_name == slug)\
159 159 .filter(RepoGroup.group_parent_id == group_parent_id)\
160 160 .scalar()
161 161
162 162 if gr:
163 163 e_dict = {
164 164 'group_name': _('This group already exists')
165 165 }
166 166 raise formencode.Invalid('', value, state,
167 167 error_dict=e_dict)
168 168
169 169 # check for same repo
170 170 repo = Repository.query()\
171 171 .filter(Repository.repo_name == slug)\
172 172 .scalar()
173 173
174 174 if repo:
175 175 e_dict = {
176 176 'group_name': _('Repository with this name already exists')
177 177 }
178 178 raise formencode.Invalid('', value, state,
179 179 error_dict=e_dict)
180 180
181 181 return _ValidReposGroup
182 182
183 183
184 184 class ValidPassword(formencode.validators.FancyValidator):
185 185
186 186 def to_python(self, value, state):
187 187
188 188 if not value:
189 189 return
190 190
191 191 if value.get('password'):
192 192 try:
193 193 value['password'] = get_crypt_password(value['password'])
194 194 except UnicodeEncodeError:
195 195 e_dict = {'password': _('Invalid characters in password')}
196 196 raise formencode.Invalid('', value, state, error_dict=e_dict)
197 197
198 198 if value.get('password_confirmation'):
199 199 try:
200 200 value['password_confirmation'] = \
201 201 get_crypt_password(value['password_confirmation'])
202 202 except UnicodeEncodeError:
203 203 e_dict = {
204 204 'password_confirmation': _('Invalid characters in password')
205 205 }
206 206 raise formencode.Invalid('', value, state, error_dict=e_dict)
207 207
208 208 if value.get('new_password'):
209 209 try:
210 210 value['new_password'] = \
211 211 get_crypt_password(value['new_password'])
212 212 except UnicodeEncodeError:
213 213 e_dict = {'new_password': _('Invalid characters in password')}
214 214 raise formencode.Invalid('', value, state, error_dict=e_dict)
215 215
216 216 return value
217 217
218 218
219 219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
220 220
221 221 def validate_python(self, value, state):
222 222
223 223 pass_val = value.get('password') or value.get('new_password')
224 224 if pass_val != value['password_confirmation']:
225 225 e_dict = {'password_confirmation':
226 226 _('Passwords do not match')}
227 227 raise formencode.Invalid('', value, state, error_dict=e_dict)
228 228
229 229
230 230 class ValidAuth(formencode.validators.FancyValidator):
231 231 messages = {
232 232 'invalid_password':_('invalid password'),
233 233 'invalid_login':_('invalid user name'),
234 234 'disabled_account':_('Your account is disabled')
235 235 }
236 236
237 237 # error mapping
238 238 e_dict = {'username': messages['invalid_login'],
239 239 'password': messages['invalid_password']}
240 240 e_dict_disable = {'username': messages['disabled_account']}
241 241
242 242 def validate_python(self, value, state):
243 243 password = value['password']
244 244 username = value['username']
245 245 user = User.get_by_username(username)
246 246
247 247 if authenticate(username, password):
248 248 return value
249 249 else:
250 250 if user and user.active is False:
251 251 log.warning('user %s is disabled' % username)
252 252 raise formencode.Invalid(
253 253 self.message('disabled_account',
254 254 state=State_obj),
255 255 value, state,
256 256 error_dict=self.e_dict_disable
257 257 )
258 258 else:
259 259 log.warning('user %s not authenticated' % username)
260 260 raise formencode.Invalid(
261 261 self.message('invalid_password',
262 262 state=State_obj), value, state,
263 263 error_dict=self.e_dict
264 264 )
265 265
266 266
267 267 class ValidRepoUser(formencode.validators.FancyValidator):
268 268
269 269 def to_python(self, value, state):
270 270 try:
271 271 User.query().filter(User.active == True)\
272 272 .filter(User.username == value).one()
273 273 except Exception:
274 274 raise formencode.Invalid(_('This username is not valid'),
275 275 value, state)
276 276 return value
277 277
278 278
279 279 def ValidRepoName(edit, old_data):
280 280 class _ValidRepoName(formencode.validators.FancyValidator):
281 281 def to_python(self, value, state):
282 282
283 283 repo_name = value.get('repo_name')
284 284
285 285 slug = repo_name_slug(repo_name)
286 286 if slug in [ADMIN_PREFIX, '']:
287 287 e_dict = {'repo_name': _('This repository name is disallowed')}
288 288 raise formencode.Invalid('', value, state, error_dict=e_dict)
289 289
290 290 if value.get('repo_group'):
291 291 gr = RepoGroup.get(value.get('repo_group'))
292 292 group_path = gr.full_path
293 293 # value needs to be aware of group name in order to check
294 294 # db key This is an actual just the name to store in the
295 295 # database
296 296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
297 297
298 298 else:
299 299 group_path = ''
300 300 repo_name_full = repo_name
301 301
302 302 value['repo_name_full'] = repo_name_full
303 303 rename = old_data.get('repo_name') != repo_name_full
304 304 create = not edit
305 305 if rename or create:
306 306
307 307 if group_path != '':
308 308 if Repository.get_by_repo_name(repo_name_full):
309 309 e_dict = {
310 310 'repo_name': _('This repository already exists in '
311 311 'a group "%s"') % gr.group_name
312 312 }
313 313 raise formencode.Invalid('', value, state,
314 314 error_dict=e_dict)
315 315 elif RepoGroup.get_by_group_name(repo_name_full):
316 316 e_dict = {
317 317 'repo_name': _('There is a group with this name '
318 318 'already "%s"') % repo_name_full
319 319 }
320 320 raise formencode.Invalid('', value, state,
321 321 error_dict=e_dict)
322 322
323 323 elif Repository.get_by_repo_name(repo_name_full):
324 324 e_dict = {'repo_name': _('This repository '
325 325 'already exists')}
326 326 raise formencode.Invalid('', value, state,
327 327 error_dict=e_dict)
328 328
329 329 return value
330 330
331 331 return _ValidRepoName
332 332
333 333
334 334 def ValidForkName(*args, **kwargs):
335 335 return ValidRepoName(*args, **kwargs)
336 336
337 337
338 338 def SlugifyName():
339 339 class _SlugifyName(formencode.validators.FancyValidator):
340 340
341 341 def to_python(self, value, state):
342 342 return repo_name_slug(value)
343 343
344 344 return _SlugifyName
345 345
346 346
347 347 def ValidCloneUri():
348 348 from mercurial.httprepo import httprepository, httpsrepository
349 349 from rhodecode.lib.utils import make_ui
350 350
351 351 class _ValidCloneUri(formencode.validators.FancyValidator):
352 352
353 353 def to_python(self, value, state):
354 354 if not value:
355 355 pass
356 356 elif value.startswith('https'):
357 357 try:
358 358 httpsrepository(make_ui('db'), value).capabilities
359 359 except Exception:
360 360 log.error(traceback.format_exc())
361 361 raise formencode.Invalid(_('invalid clone url'), value,
362 362 state)
363 363 elif value.startswith('http'):
364 364 try:
365 365 httprepository(make_ui('db'), value).capabilities
366 366 except Exception:
367 367 log.error(traceback.format_exc())
368 368 raise formencode.Invalid(_('invalid clone url'), value,
369 369 state)
370 370 else:
371 371 raise formencode.Invalid(_('Invalid clone url, provide a '
372 372 'valid clone http\s url'), value,
373 373 state)
374 374 return value
375 375
376 376 return _ValidCloneUri
377 377
378 378
379 379 def ValidForkType(old_data):
380 380 class _ValidForkType(formencode.validators.FancyValidator):
381 381
382 382 def to_python(self, value, state):
383 383 if old_data['repo_type'] != value:
384 384 raise formencode.Invalid(_('Fork have to be the same '
385 385 'type as original'), value, state)
386 386
387 387 return value
388 388 return _ValidForkType
389 389
390 390
391 class ValidPerms(formencode.validators.FancyValidator):
392 messages = {'perm_new_member_name': _('This username or users group name'
393 ' is not valid')}
391 def ValidPerms(type_='repo'):
392 if type_ == 'group':
393 EMPTY_PERM = 'group.none'
394 elif type_ == 'repo':
395 EMPTY_PERM = 'repository.none'
394 396
395 def to_python(self, value, state):
396 perms_update = []
397 perms_new = []
398 #build a list of permission to update and new permission to create
399 for k, v in value.items():
400 #means new added member to permissions
401 if k.startswith('perm_new_member'):
402 new_perm = value.get('perm_new_member', False)
403 new_member = value.get('perm_new_member_name', False)
404 new_type = value.get('perm_new_member_type')
397 class _ValidPerms(formencode.validators.FancyValidator):
398 messages = {
399 'perm_new_member_name':
400 _('This username or users group name is not valid')
401 }
402
403 def to_python(self, value, state):
404 perms_update = []
405 perms_new = []
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:
407 if (new_member, new_perm, new_type) not in perms_new:
408 perms_new.append((new_member, new_perm, new_type))
409 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
410 member = k[7:]
411 t = {'u': 'user',
412 'g': 'users_group'
413 }[k[0]]
414 if member == 'default':
415 if value['private']:
416 #set none for default when updating to private repo
417 v = 'repository.none'
418 perms_update.append((member, v, t))
414 if new_member and new_perm:
415 if (new_member, new_perm, new_type) not in perms_new:
416 perms_new.append((new_member, new_perm, new_type))
417 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
418 member = k[7:]
419 t = {'u': 'user',
420 'g': 'users_group'
421 }[k[0]]
422 if member == 'default':
423 if value.get('private'):
424 # set none for default when updating to private repo
425 v = EMPTY_PERM
426 perms_update.append((member, v, t))
419 427
420 value['perms_updates'] = perms_update
421 value['perms_new'] = perms_new
428 value['perms_updates'] = perms_update
429 value['perms_new'] = perms_new
422 430
423 #update permissions
424 for k, v, t in perms_new:
425 try:
426 if t is 'user':
427 self.user_db = User.query()\
428 .filter(User.active == True)\
429 .filter(User.username == k).one()
430 if t is 'users_group':
431 self.user_db = UsersGroup.query()\
432 .filter(UsersGroup.users_group_active == True)\
433 .filter(UsersGroup.users_group_name == k).one()
431 # update permissions
432 for k, v, t in perms_new:
433 try:
434 if t is 'user':
435 self.user_db = User.query()\
436 .filter(User.active == True)\
437 .filter(User.username == k).one()
438 if t is 'users_group':
439 self.user_db = UsersGroup.query()\
440 .filter(UsersGroup.users_group_active == True)\
441 .filter(UsersGroup.users_group_name == k).one()
434 442
435 except Exception:
436 msg = self.message('perm_new_member_name',
437 state=State_obj)
438 raise formencode.Invalid(
439 msg, value, state, error_dict={'perm_new_member_name': msg}
440 )
441 return value
443 except Exception:
444 msg = self.message('perm_new_member_name',
445 state=State_obj)
446 raise formencode.Invalid(
447 msg, value, state, error_dict={'perm_new_member_name': msg}
448 )
449 return value
450 return _ValidPerms
442 451
443 452
444 453 class ValidSettings(formencode.validators.FancyValidator):
445 454
446 455 def to_python(self, value, state):
447 456 # settings form can't edit user
448 457 if 'user' in value:
449 458 del['value']['user']
450 459 return value
451 460
452 461
453 462 class ValidPath(formencode.validators.FancyValidator):
454 463 def to_python(self, value, state):
455 464
456 465 if not os.path.isdir(value):
457 466 msg = _('This is not a valid path')
458 467 raise formencode.Invalid(msg, value, state,
459 468 error_dict={'paths_root_path': msg})
460 469 return value
461 470
462 471
463 472 def UniqSystemEmail(old_data):
464 473 class _UniqSystemEmail(formencode.validators.FancyValidator):
465 474 def to_python(self, value, state):
466 475 value = value.lower()
467 476 if old_data.get('email', '').lower() != value:
468 477 user = User.get_by_email(value, case_insensitive=True)
469 478 if user:
470 479 raise formencode.Invalid(
471 480 _("This e-mail address is already taken"), value, state
472 481 )
473 482 return value
474 483
475 484 return _UniqSystemEmail
476 485
477 486
478 487 class ValidSystemEmail(formencode.validators.FancyValidator):
479 488 def to_python(self, value, state):
480 489 value = value.lower()
481 490 user = User.get_by_email(value, case_insensitive=True)
482 491 if user is None:
483 492 raise formencode.Invalid(
484 493 _("This e-mail address doesn't exist."), value, state
485 494 )
486 495
487 496 return value
488 497
489 498
490 499 class LdapLibValidator(formencode.validators.FancyValidator):
491 500
492 501 def to_python(self, value, state):
493 502
494 503 try:
495 504 import ldap
496 505 except ImportError:
497 506 raise LdapImportError
498 507 return value
499 508
500 509
501 510 class AttrLoginValidator(formencode.validators.FancyValidator):
502 511
503 512 def to_python(self, value, state):
504 513
505 514 if not value or not isinstance(value, (str, unicode)):
506 515 raise formencode.Invalid(
507 516 _("The LDAP Login attribute of the CN must be specified - "
508 517 "this is the name of the attribute that is equivalent "
509 518 "to 'username'"), value, state
510 519 )
511 520
512 521 return value
513 522
514 523
515 524 #==============================================================================
516 525 # FORMS
517 526 #==============================================================================
518 527 class LoginForm(formencode.Schema):
519 528 allow_extra_fields = True
520 529 filter_extra_fields = True
521 530 username = UnicodeString(
522 531 strip=True,
523 532 min=1,
524 533 not_empty=True,
525 534 messages={
526 535 'empty': _('Please enter a login'),
527 536 'tooShort': _('Enter a value %(min)i characters long or more')}
528 537 )
529 538
530 539 password = UnicodeString(
531 540 strip=True,
532 541 min=3,
533 542 not_empty=True,
534 543 messages={
535 544 'empty': _('Please enter a password'),
536 545 'tooShort': _('Enter %(min)i characters or more')}
537 546 )
538 547
539 548 remember = StringBoolean(if_missing=False)
540 549
541 550 chained_validators = [ValidAuth]
542 551
543 552
544 553 def UserForm(edit=False, old_data={}):
545 554 class _UserForm(formencode.Schema):
546 555 allow_extra_fields = True
547 556 filter_extra_fields = True
548 557 username = All(UnicodeString(strip=True, min=1, not_empty=True),
549 558 ValidUsername(edit, old_data))
550 559 if edit:
551 560 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
552 561 password_confirmation = All(UnicodeString(strip=True, min=6,
553 562 not_empty=False))
554 563 admin = StringBoolean(if_missing=False)
555 564 else:
556 565 password = All(UnicodeString(strip=True, min=6, not_empty=True))
557 566 password_confirmation = All(UnicodeString(strip=True, min=6,
558 567 not_empty=False))
559 568
560 569 active = StringBoolean(if_missing=False)
561 570 name = UnicodeString(strip=True, min=1, not_empty=False)
562 571 lastname = UnicodeString(strip=True, min=1, not_empty=False)
563 572 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
564 573
565 574 chained_validators = [ValidPasswordsMatch, ValidPassword]
566 575
567 576 return _UserForm
568 577
569 578
570 579 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
571 580 class _UsersGroupForm(formencode.Schema):
572 581 allow_extra_fields = True
573 582 filter_extra_fields = True
574 583
575 584 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
576 585 ValidUsersGroup(edit, old_data))
577 586
578 587 users_group_active = StringBoolean(if_missing=False)
579 588
580 589 if edit:
581 590 users_group_members = OneOf(available_members, hideList=False,
582 591 testValueList=True,
583 592 if_missing=None, not_empty=False)
584 593
585 594 return _UsersGroupForm
586 595
587 596
588 597 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
589 598 class _ReposGroupForm(formencode.Schema):
590 599 allow_extra_fields = True
591 filter_extra_fields = True
600 filter_extra_fields = False
592 601
593 602 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
594 603 SlugifyName())
595 604 group_description = UnicodeString(strip=True, min=1,
596 605 not_empty=True)
597 606 group_parent_id = OneOf(available_groups, hideList=False,
598 607 testValueList=True,
599 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 612 return _ReposGroupForm
604 613
605 614
606 615 def RegisterForm(edit=False, old_data={}):
607 616 class _RegisterForm(formencode.Schema):
608 617 allow_extra_fields = True
609 618 filter_extra_fields = True
610 619 username = All(ValidUsername(edit, old_data),
611 620 UnicodeString(strip=True, min=1, not_empty=True))
612 621 password = All(UnicodeString(strip=True, min=6, not_empty=True))
613 622 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
614 623 active = StringBoolean(if_missing=False)
615 624 name = UnicodeString(strip=True, min=1, not_empty=False)
616 625 lastname = UnicodeString(strip=True, min=1, not_empty=False)
617 626 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
618 627
619 628 chained_validators = [ValidPasswordsMatch, ValidPassword]
620 629
621 630 return _RegisterForm
622 631
623 632
624 633 def PasswordResetForm():
625 634 class _PasswordResetForm(formencode.Schema):
626 635 allow_extra_fields = True
627 636 filter_extra_fields = True
628 637 email = All(ValidSystemEmail(), Email(not_empty=True))
629 638 return _PasswordResetForm
630 639
631 640
632 641 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
633 642 repo_groups=[]):
634 643 class _RepoForm(formencode.Schema):
635 644 allow_extra_fields = True
636 645 filter_extra_fields = False
637 646 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
638 647 SlugifyName())
639 648 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
640 649 ValidCloneUri()())
641 650 repo_group = OneOf(repo_groups, hideList=True)
642 651 repo_type = OneOf(supported_backends)
643 652 description = UnicodeString(strip=True, min=1, not_empty=True)
644 653 private = StringBoolean(if_missing=False)
645 654 enable_statistics = StringBoolean(if_missing=False)
646 655 enable_downloads = StringBoolean(if_missing=False)
647 656
648 657 if edit:
649 658 #this is repo owner
650 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 662 return _RepoForm
654 663
655 664
656 665 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
657 666 repo_groups=[]):
658 667 class _RepoForkForm(formencode.Schema):
659 668 allow_extra_fields = True
660 669 filter_extra_fields = False
661 670 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
662 671 SlugifyName())
663 672 repo_group = OneOf(repo_groups, hideList=True)
664 673 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
665 674 description = UnicodeString(strip=True, min=1, not_empty=True)
666 675 private = StringBoolean(if_missing=False)
667 676 copy_permissions = StringBoolean(if_missing=False)
668 677 update_after_clone = StringBoolean(if_missing=False)
669 678 fork_parent_id = UnicodeString()
670 679 chained_validators = [ValidForkName(edit, old_data)]
671 680
672 681 return _RepoForkForm
673 682
674 683
675 684 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
676 685 repo_groups=[]):
677 686 class _RepoForm(formencode.Schema):
678 687 allow_extra_fields = True
679 688 filter_extra_fields = False
680 689 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
681 690 SlugifyName())
682 691 description = UnicodeString(strip=True, min=1, not_empty=True)
683 692 repo_group = OneOf(repo_groups, hideList=True)
684 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 696 ValidSettings]
688 697 return _RepoForm
689 698
690 699
691 700 def ApplicationSettingsForm():
692 701 class _ApplicationSettingsForm(formencode.Schema):
693 702 allow_extra_fields = True
694 703 filter_extra_fields = False
695 704 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
696 705 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
697 706 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
698 707
699 708 return _ApplicationSettingsForm
700 709
701 710
702 711 def ApplicationUiSettingsForm():
703 712 class _ApplicationUiSettingsForm(formencode.Schema):
704 713 allow_extra_fields = True
705 714 filter_extra_fields = False
706 715 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
707 716 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
708 717 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
709 718 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
710 719 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
711 720 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
712 721
713 722 return _ApplicationUiSettingsForm
714 723
715 724
716 725 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
717 726 class _DefaultPermissionsForm(formencode.Schema):
718 727 allow_extra_fields = True
719 728 filter_extra_fields = True
720 729 overwrite_default = StringBoolean(if_missing=False)
721 730 anonymous = OneOf(['True', 'False'], if_missing=False)
722 731 default_perm = OneOf(perms_choices)
723 732 default_register = OneOf(register_choices)
724 733 default_create = OneOf(create_choices)
725 734
726 735 return _DefaultPermissionsForm
727 736
728 737
729 738 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
730 739 class _LdapSettingsForm(formencode.Schema):
731 740 allow_extra_fields = True
732 741 filter_extra_fields = True
733 742 pre_validators = [LdapLibValidator]
734 743 ldap_active = StringBoolean(if_missing=False)
735 744 ldap_host = UnicodeString(strip=True,)
736 745 ldap_port = Number(strip=True,)
737 746 ldap_tls_kind = OneOf(tls_kind_choices)
738 747 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
739 748 ldap_dn_user = UnicodeString(strip=True,)
740 749 ldap_dn_pass = UnicodeString(strip=True,)
741 750 ldap_base_dn = UnicodeString(strip=True,)
742 751 ldap_filter = UnicodeString(strip=True,)
743 752 ldap_search_scope = OneOf(search_scope_choices)
744 753 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
745 754 ldap_attr_firstname = UnicodeString(strip=True,)
746 755 ldap_attr_lastname = UnicodeString(strip=True,)
747 756 ldap_attr_email = UnicodeString(strip=True,)
748 757
749 758 return _LdapSettingsForm
@@ -1,219 +1,216 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.notification
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Model for notifications
7 7
8 8
9 9 :created_on: Nov 20, 2011
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 30 import datetime
31 31
32 32 from pylons.i18n.translation import _
33 33
34 34 import rhodecode
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.model import BaseModel
37 37 from rhodecode.model.db import Notification, User, UserNotification
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class NotificationModel(BaseModel):
43 43
44 44 def __get_user(self, user):
45 if isinstance(user, basestring):
46 return User.get_by_username(username=user)
47 else:
48 return self._get_instance(User, user)
45 return self._get_instance(User, user, callback=User.get_by_username)
49 46
50 47 def __get_notification(self, notification):
51 48 if isinstance(notification, Notification):
52 49 return notification
53 50 elif isinstance(notification, int):
54 51 return Notification.get(notification)
55 52 else:
56 53 if notification:
57 54 raise Exception('notification must be int or Instance'
58 55 ' of Notification got %s' % type(notification))
59 56
60 57 def create(self, created_by, subject, body, recipients=None,
61 58 type_=Notification.TYPE_MESSAGE, with_email=True,
62 59 email_kwargs={}):
63 60 """
64 61
65 62 Creates notification of given type
66 63
67 64 :param created_by: int, str or User instance. User who created this
68 65 notification
69 66 :param subject:
70 67 :param body:
71 68 :param recipients: list of int, str or User objects, when None
72 69 is given send to all admins
73 70 :param type_: type of notification
74 71 :param with_email: send email with this notification
75 72 :param email_kwargs: additional dict to pass as args to email template
76 73 """
77 74 from rhodecode.lib.celerylib import tasks, run_task
78 75
79 76 if recipients and not getattr(recipients, '__iter__', False):
80 77 raise Exception('recipients must be a list of iterable')
81 78
82 79 created_by_obj = self.__get_user(created_by)
83 80
84 81 if recipients:
85 82 recipients_objs = []
86 83 for u in recipients:
87 84 obj = self.__get_user(u)
88 85 if obj:
89 86 recipients_objs.append(obj)
90 87 recipients_objs = set(recipients_objs)
91 88 else:
92 89 # empty recipients means to all admins
93 90 recipients_objs = User.query().filter(User.admin == True).all()
94 91
95 92 notif = Notification.create(created_by=created_by_obj, subject=subject,
96 93 body=body, recipients=recipients_objs,
97 94 type_=type_)
98 95
99 96 if with_email is False:
100 97 return notif
101 98
102 99 # send email with notification
103 100 for rec in recipients_objs:
104 101 email_subject = NotificationModel().make_description(notif, False)
105 102 type_ = type_
106 103 email_body = body
107 104 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
108 105 kwargs.update(email_kwargs)
109 106 email_body_html = EmailNotificationModel()\
110 107 .get_email_tmpl(type_, **kwargs)
111 108 run_task(tasks.send_email, rec.email, email_subject, email_body,
112 109 email_body_html)
113 110
114 111 return notif
115 112
116 113 def delete(self, user, notification):
117 114 # we don't want to remove actual notification just the assignment
118 115 try:
119 116 notification = self.__get_notification(notification)
120 117 user = self.__get_user(user)
121 118 if notification and user:
122 119 obj = UserNotification.query()\
123 120 .filter(UserNotification.user == user)\
124 121 .filter(UserNotification.notification
125 122 == notification)\
126 123 .one()
127 124 self.sa.delete(obj)
128 125 return True
129 126 except Exception:
130 127 log.error(traceback.format_exc())
131 128 raise
132 129
133 130 def get_for_user(self, user):
134 131 user = self.__get_user(user)
135 132 return user.notifications
136 133
137 134 def mark_all_read_for_user(self, user):
138 135 user = self.__get_user(user)
139 136 UserNotification.query()\
140 137 .filter(UserNotification.read==False)\
141 138 .update({'read': True})
142 139
143 140 def get_unread_cnt_for_user(self, user):
144 141 user = self.__get_user(user)
145 142 return UserNotification.query()\
146 143 .filter(UserNotification.read == False)\
147 144 .filter(UserNotification.user == user).count()
148 145
149 146 def get_unread_for_user(self, user):
150 147 user = self.__get_user(user)
151 148 return [x.notification for x in UserNotification.query()\
152 149 .filter(UserNotification.read == False)\
153 150 .filter(UserNotification.user == user).all()]
154 151
155 152 def get_user_notification(self, user, notification):
156 153 user = self.__get_user(user)
157 154 notification = self.__get_notification(notification)
158 155
159 156 return UserNotification.query()\
160 157 .filter(UserNotification.notification == notification)\
161 158 .filter(UserNotification.user == user).scalar()
162 159
163 160 def make_description(self, notification, show_age=True):
164 161 """
165 162 Creates a human readable description based on properties
166 163 of notification object
167 164 """
168 165
169 166 _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
170 167 notification.TYPE_MESSAGE:_('sent message'),
171 168 notification.TYPE_MENTION:_('mentioned you'),
172 169 notification.TYPE_REGISTRATION:_('registered in RhodeCode')}
173 170
174 171 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
175 172
176 173 tmpl = "%(user)s %(action)s %(when)s"
177 174 if show_age:
178 175 when = h.age(notification.created_on)
179 176 else:
180 177 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
181 178 when = DTF(notification.created_on)
182 179 data = dict(user=notification.created_by_user.username,
183 180 action=_map[notification.type_],
184 181 when=when)
185 182 return tmpl % data
186 183
187 184
188 185 class EmailNotificationModel(BaseModel):
189 186
190 187 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
191 188 TYPE_PASSWORD_RESET = 'passoword_link'
192 189 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
193 190 TYPE_DEFAULT = 'default'
194 191
195 192 def __init__(self):
196 193 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
197 194 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
198 195
199 196 self.email_types = {
200 197 self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
201 198 self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
202 199 self.TYPE_REGISTRATION:'email_templates/registration.html',
203 200 self.TYPE_DEFAULT:'email_templates/default.html'
204 201 }
205 202
206 203 def get_email_tmpl(self, type_, **kwargs):
207 204 """
208 205 return generated template for email based on given type
209 206
210 207 :param type_:
211 208 """
212 209
213 210 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
214 211 email_template = self._tmpl_lookup.get_template(base)
215 212 # translator inject
216 213 _kwargs = {'_':_}
217 214 _kwargs.update(kwargs)
218 215 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
219 216 return email_template.render(**_kwargs)
@@ -1,432 +1,491 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.repo
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository model for rhodecode
7 7
8 8 :created_on: Jun 5, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import shutil
27 27 import logging
28 28 import traceback
29 29 from datetime import datetime
30 30
31 from vcs.utils.lazy import LazyProperty
32 31 from vcs.backends import get_backend
33 32
33 from rhodecode.lib import LazyProperty
34 34 from rhodecode.lib import safe_str, safe_unicode
35 35 from rhodecode.lib.caching_query import FromCache
36 36 from rhodecode.lib.hooks import log_create_repository
37 37
38 38 from rhodecode.model import BaseModel
39 39 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
40 40 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
41 41
42
42 43 log = logging.getLogger(__name__)
43 44
44 45
45 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 67 @LazyProperty
48 68 def repos_path(self):
49 69 """
50 70 Get's the repositories root path from database
51 71 """
52 72
53 73 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
54 74 return q.ui_value
55 75
56 76 def get(self, repo_id, cache=False):
57 77 repo = self.sa.query(Repository)\
58 78 .filter(Repository.repo_id == repo_id)
59 79
60 80 if cache:
61 81 repo = repo.options(FromCache("sql_cache_short",
62 82 "get_repo_%s" % repo_id))
63 83 return repo.scalar()
64 84
65 85 def get_by_repo_name(self, repo_name, cache=False):
66 86 repo = self.sa.query(Repository)\
67 87 .filter(Repository.repo_name == repo_name)
68 88
69 89 if cache:
70 90 repo = repo.options(FromCache("sql_cache_short",
71 91 "get_repo_%s" % repo_name))
72 92 return repo.scalar()
73 93
74 94 def get_users_js(self):
75 95
76 96 users = self.sa.query(User).filter(User.active == True).all()
77 97 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
78 98 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
79 99 u.lastname, u.username)
80 100 for u in users])
81 101 return users_array
82 102
83 103 def get_users_groups_js(self):
84 104 users_groups = self.sa.query(UsersGroup)\
85 105 .filter(UsersGroup.users_group_active == True).all()
86 106
87 107 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
88 108
89 109 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
90 110 (gr.users_group_id, gr.users_group_name,
91 111 len(gr.members))
92 112 for gr in users_groups])
93 113 return users_groups_array
94 114
95 115 def _get_defaults(self, repo_name):
96 116 """
97 117 Get's information about repository, and returns a dict for
98 118 usage in forms
99 119
100 120 :param repo_name:
101 121 """
102 122
103 123 repo_info = Repository.get_by_repo_name(repo_name)
104 124
105 125 if repo_info is None:
106 126 return None
107 127
108 128 defaults = repo_info.get_dict()
109 129 group, repo_name = repo_info.groups_and_repo
110 130 defaults['repo_name'] = repo_name
111 131 defaults['repo_group'] = getattr(group[-1] if group else None,
112 132 'group_id', None)
113 133
114 134 # fill owner
115 135 if repo_info.user:
116 136 defaults.update({'user': repo_info.user.username})
117 137 else:
118 138 replacement_user = User.query().filter(User.admin ==
119 139 True).first().username
120 140 defaults.update({'user': replacement_user})
121 141
122 142 # fill repository users
123 143 for p in repo_info.repo_to_perm:
124 144 defaults.update({'u_perm_%s' % p.user.username:
125 145 p.permission.permission_name})
126 146
127 147 # fill repository groups
128 148 for p in repo_info.users_group_to_perm:
129 149 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
130 150 p.permission.permission_name})
131 151
132 152 return defaults
133 153
134 154 def update(self, repo_name, form_data):
135 155 try:
136 156 cur_repo = self.get_by_repo_name(repo_name, cache=False)
137 157
138 158 # update permissions
139 159 for member, perm, member_type in form_data['perms_updates']:
140 160 if member_type == 'user':
141 _member = User.get_by_username(member)
142 r2p = self.sa.query(UserRepoToPerm)\
143 .filter(UserRepoToPerm.user == _member)\
144 .filter(UserRepoToPerm.repository == cur_repo)\
145 .one()
146
147 r2p.permission = self.sa.query(Permission)\
148 .filter(Permission.permission_name ==
149 perm).scalar()
150 self.sa.add(r2p)
161 # this updates existing one
162 RepoModel().grant_user_permission(
163 repo=cur_repo, user=member, perm=perm
164 )
151 165 else:
152 g2p = self.sa.query(UsersGroupRepoToPerm)\
153 .filter(UsersGroupRepoToPerm.users_group ==
154 UsersGroup.get_by_group_name(member))\
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
166 RepoModel().grant_users_group_permission(
167 repo=cur_repo, group_name=member, perm=perm
168 )
163 169 # set new permissions
164 170 for member, perm, member_type in form_data['perms_new']:
165 171 if member_type == 'user':
166 r2p = UserRepoToPerm()
167 r2p.repository = cur_repo
168 r2p.user = User.get_by_username(member)
169
170 r2p.permission = self.sa.query(Permission)\
171 .filter(Permission.
172 permission_name == perm)\
173 .scalar()
174 self.sa.add(r2p)
172 RepoModel().grant_user_permission(
173 repo=cur_repo, user=member, perm=perm
174 )
175 175 else:
176 g2p = UsersGroupRepoToPerm()
177 g2p.repository = cur_repo
178 g2p.users_group = UsersGroup.get_by_group_name(member)
179 g2p.permission = self.sa.query(Permission)\
180 .filter(Permission.
181 permission_name == perm)\
182 .scalar()
183 self.sa.add(g2p)
176 RepoModel().grant_users_group_permission(
177 repo=cur_repo, group_name=member, perm=perm
178 )
184 179
185 180 # update current repo
186 181 for k, v in form_data.items():
187 182 if k == 'user':
188 183 cur_repo.user = User.get_by_username(v)
189 184 elif k == 'repo_name':
190 185 pass
191 186 elif k == 'repo_group':
192 187 cur_repo.group = RepoGroup.get(v)
193 188
194 189 else:
195 190 setattr(cur_repo, k, v)
196 191
197 192 new_name = cur_repo.get_new_name(form_data['repo_name'])
198 193 cur_repo.repo_name = new_name
199 194
200 195 self.sa.add(cur_repo)
201 196
202 197 if repo_name != new_name:
203 198 # rename repository
204 199 self.__rename_repo(old=repo_name, new=new_name)
205 200
206 201 return cur_repo
207 202 except:
208 203 log.error(traceback.format_exc())
209 204 raise
210 205
211 206 def create(self, form_data, cur_user, just_db=False, fork=False):
212 207 from rhodecode.model.scm import ScmModel
213 208
214 209 try:
215 210 if fork:
216 211 fork_parent_id = form_data['fork_parent_id']
217 212
218 213 # repo name is just a name of repository
219 214 # while repo_name_full is a full qualified name that is combined
220 215 # with name and path of group
221 216 repo_name = form_data['repo_name']
222 217 repo_name_full = form_data['repo_name_full']
223 218
224 219 new_repo = Repository()
225 220 new_repo.enable_statistics = False
226 221
227 222 for k, v in form_data.items():
228 223 if k == 'repo_name':
229 224 v = repo_name_full
230 225 if k == 'repo_group':
231 226 k = 'group_id'
232 227 if k == 'description':
233 228 v = v or repo_name
234 229
235 230 setattr(new_repo, k, v)
236 231
237 232 if fork:
238 233 parent_repo = Repository.get(fork_parent_id)
239 234 new_repo.fork = parent_repo
240 235
241 236 new_repo.user_id = cur_user.user_id
242 237 self.sa.add(new_repo)
243 238
244 239 def _create_default_perms():
245 240 # create default permission
246 241 repo_to_perm = UserRepoToPerm()
247 242 default = 'repository.read'
248 243 for p in User.get_by_username('default').user_perms:
249 244 if p.permission.permission_name.startswith('repository.'):
250 245 default = p.permission.permission_name
251 246 break
252 247
253 248 default_perm = 'repository.none' if form_data['private'] else default
254 249
255 250 repo_to_perm.permission_id = self.sa.query(Permission)\
256 251 .filter(Permission.permission_name == default_perm)\
257 252 .one().permission_id
258 253
259 254 repo_to_perm.repository = new_repo
260 255 repo_to_perm.user_id = User.get_by_username('default').user_id
261 256
262 257 self.sa.add(repo_to_perm)
263 258
264 259 if fork:
265 260 if form_data.get('copy_permissions'):
266 261 repo = Repository.get(fork_parent_id)
267 262 user_perms = UserRepoToPerm.query()\
268 263 .filter(UserRepoToPerm.repository == repo).all()
269 264 group_perms = UsersGroupRepoToPerm.query()\
270 265 .filter(UsersGroupRepoToPerm.repository == repo).all()
271 266
272 267 for perm in user_perms:
273 268 UserRepoToPerm.create(perm.user, new_repo,
274 269 perm.permission)
275 270
276 271 for perm in group_perms:
277 272 UsersGroupRepoToPerm.create(perm.users_group, new_repo,
278 273 perm.permission)
279 274 else:
280 275 _create_default_perms()
281 276 else:
282 277 _create_default_perms()
283 278
284 279 if not just_db:
285 280 self.__create_repo(repo_name, form_data['repo_type'],
286 281 form_data['repo_group'],
287 282 form_data['clone_uri'])
288 283
289 284 # now automatically start following this repository as owner
290 285 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
291 286 cur_user.user_id)
292 287 log_create_repository(new_repo.get_dict(),
293 288 created_by=cur_user.username)
294 289 return new_repo
295 290 except:
296 291 log.error(traceback.format_exc())
297 292 raise
298 293
299 294 def create_fork(self, form_data, cur_user):
300 295 """
301 296 Simple wrapper into executing celery task for fork creation
302 297
303 298 :param form_data:
304 299 :param cur_user:
305 300 """
306 301 from rhodecode.lib.celerylib import tasks, run_task
307 302 run_task(tasks.create_repo_fork, form_data, cur_user)
308 303
309 304 def delete(self, repo):
310 305 try:
311 306 self.sa.delete(repo)
312 307 self.__delete_repo(repo)
313 308 except:
314 309 log.error(traceback.format_exc())
315 310 raise
316 311
317 def delete_perm_user(self, form_data, repo_name):
318 try:
319 obj = self.sa.query(UserRepoToPerm)\
320 .filter(UserRepoToPerm.repository \
321 == self.get_by_repo_name(repo_name))\
322 .filter(UserRepoToPerm.user_id == form_data['user_id']).one()
323 self.sa.delete(obj)
324 except:
325 log.error(traceback.format_exc())
326 raise
312 def grant_user_permission(self, repo, user, perm):
313 """
314 Grant permission for user on given repository, or update existing one
315 if found
316
317 :param repo: Instance of Repository, repository_id, or repository name
318 :param user: Instance of User, user_id or username
319 :param perm: Instance of Permission, or permission_name
320 """
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):
329 try:
330 obj = self.sa.query(UsersGroupRepoToPerm)\
331 .filter(UsersGroupRepoToPerm.repository \
332 == self.get_by_repo_name(repo_name))\
333 .filter(UsersGroupRepoToPerm.users_group_id
334 == form_data['users_group_id']).one()
335 self.sa.delete(obj)
336 except:
337 log.error(traceback.format_exc())
338 raise
354 def grant_users_group_permission(self, repo, group_name, perm):
355 """
356 Grant permission for users group on given repository, or update
357 existing one if found
358
359 :param repo: Instance of Repository, repository_id, or repository name
360 :param group_name: Instance of UserGroup, users_group_id,
361 or users group name
362 :param perm: Instance of Permission, or permission_name
363 """
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 400 def delete_stats(self, repo_name):
341 401 """
342 402 removes stats for given repo
343 403
344 404 :param repo_name:
345 405 """
346 406 try:
347 407 obj = self.sa.query(Statistics)\
348 .filter(Statistics.repository == \
349 self.get_by_repo_name(repo_name)).one()
408 .filter(Statistics.repository ==
409 self.get_by_repo_name(repo_name))\
410 .one()
350 411 self.sa.delete(obj)
351 412 except:
352 413 log.error(traceback.format_exc())
353 414 raise
354 415
355 416 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
356 417 """
357 418 makes repository on filesystem. It's group aware means it'll create
358 419 a repository within a group, and alter the paths accordingly of
359 420 group location
360 421
361 422 :param repo_name:
362 423 :param alias:
363 424 :param parent_id:
364 425 :param clone_uri:
365 426 """
366 427 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
367 428
368 429 if new_parent_id:
369 430 paths = RepoGroup.get(new_parent_id)\
370 431 .full_path.split(RepoGroup.url_sep())
371 432 new_parent_path = os.sep.join(paths)
372 433 else:
373 434 new_parent_path = ''
374 435
375 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 438 [self.repos_path, new_parent_path, repo_name]))
378 439
379
380 440 # check if this path is not a repository
381 441 if is_valid_repo(repo_path, self.repos_path):
382 442 raise Exception('This path %s is a valid repository' % repo_path)
383 443
384 444 # check if this path is a group
385 445 if is_valid_repos_group(repo_path, self.repos_path):
386 446 raise Exception('This path %s is a valid group' % repo_path)
387 447
388 448 log.info('creating repo %s in %s @ %s' % (
389 449 repo_name, safe_unicode(repo_path), clone_uri
390 450 )
391 451 )
392 452 backend = get_backend(alias)
393 453
394 454 backend(repo_path, create=True, src_url=clone_uri)
395 455
396
397 456 def __rename_repo(self, old, new):
398 457 """
399 458 renames repository on filesystem
400 459
401 460 :param old: old name
402 461 :param new: new name
403 462 """
404 463 log.info('renaming repo from %s to %s' % (old, new))
405 464
406 465 old_path = os.path.join(self.repos_path, old)
407 466 new_path = os.path.join(self.repos_path, new)
408 467 if os.path.isdir(new_path):
409 raise Exception('Was trying to rename to already existing dir %s' \
410 % new_path)
468 raise Exception(
469 'Was trying to rename to already existing dir %s' % new_path
470 )
411 471 shutil.move(old_path, new_path)
412 472
413 473 def __delete_repo(self, repo):
414 474 """
415 475 removes repo from filesystem, the removal is acctually made by
416 476 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
417 477 repository is no longer valid for rhodecode, can be undeleted later on
418 478 by reverting the renames on this repository
419 479
420 480 :param repo: repo object
421 481 """
422 482 rm_path = os.path.join(self.repos_path, repo.repo_name)
423 483 log.info("Removing %s" % (rm_path))
424 484 # disable hg/git
425 485 alias = repo.repo_type
426 486 shutil.move(os.path.join(rm_path, '.%s' % alias),
427 487 os.path.join(rm_path, 'rm__.%s' % alias))
428 488 # disable repo
429 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
430 % (datetime.today()\
431 .strftime('%Y%m%d_%H%M%S_%f'),
432 repo.repo_name)))
489 _d = 'rm__%s__%s' % (datetime.now().strftime('%Y%m%d_%H%M%S_%f'),
490 repo.repo_name)
491 shutil.move(rm_path, os.path.join(self.repos_path, _d))
@@ -1,97 +1,112 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.users_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 repository permission model for RhodeCode
7 7
8 8 :created_on: Oct 1, 2011
9 9 :author: nvinot, marcink
10 10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 11 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import logging
28 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 32 log = logging.getLogger(__name__)
32 33
33 34
34 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 48 def get_user_permission(self, repository, user):
49 repository = self.__get_repo(repository)
50 user = self.__get_user(user)
51
37 52 return UserRepoToPerm.query() \
38 53 .filter(UserRepoToPerm.user == user) \
39 54 .filter(UserRepoToPerm.repository == repository) \
40 55 .scalar()
41 56
42 57 def update_user_permission(self, repository, user, permission):
43 58 permission = Permission.get_by_key(permission)
44 59 current = self.get_user_permission(repository, user)
45 60 if current:
46 61 if not current.permission is permission:
47 62 current.permission = permission
48 63 else:
49 64 p = UserRepoToPerm()
50 65 p.user = user
51 66 p.repository = repository
52 67 p.permission = permission
53 68 self.sa.add(p)
54 69
55 70 def delete_user_permission(self, repository, user):
56 71 current = self.get_user_permission(repository, user)
57 72 if current:
58 73 self.sa.delete(current)
59 74
60 75 def get_users_group_permission(self, repository, users_group):
61 76 return UsersGroupRepoToPerm.query() \
62 77 .filter(UsersGroupRepoToPerm.users_group == users_group) \
63 78 .filter(UsersGroupRepoToPerm.repository == repository) \
64 79 .scalar()
65 80
66 81 def update_users_group_permission(self, repository, users_group,
67 82 permission):
68 83 permission = Permission.get_by_key(permission)
69 84 current = self.get_users_group_permission(repository, users_group)
70 85 if current:
71 86 if not current.permission is permission:
72 87 current.permission = permission
73 88 else:
74 89 p = UsersGroupRepoToPerm()
75 90 p.users_group = users_group
76 91 p.repository = repository
77 92 p.permission = permission
78 93 self.sa.add(p)
79 94
80 95 def delete_users_group_permission(self, repository, users_group):
81 96 current = self.get_users_group_permission(repository, users_group)
82 97 if current:
83 98 self.sa.delete(current)
84 99
85 100 def update_or_delete_user_permission(self, repository, user, permission):
86 101 if permission:
87 102 self.update_user_permission(repository, user, permission)
88 103 else:
89 104 self.delete_user_permission(repository, user)
90 105
91 106 def update_or_delete_users_group_permission(self, repository, user_group,
92 107 permission):
93 108 if permission:
94 109 self.update_users_group_permission(repository, user_group,
95 110 permission)
96 111 else:
97 112 self.delete_users_group_permission(repository, user_group)
@@ -1,156 +1,310 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users groups model for RhodeCode
7 7
8 8 :created_on: Jan 25, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import traceback
29 29 import shutil
30 30
31 from pylons.i18n.translation import _
32
33 from vcs.utils.lazy import LazyProperty
31 from rhodecode.lib import LazyProperty
34 32
35 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 37 log = logging.getLogger(__name__)
39 38
40 39
41 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 57 @LazyProperty
44 58 def repos_path(self):
45 59 """
46 60 Get's the repositories root path from database
47 61 """
48 62
49 63 q = RhodeCodeUi.get_by_key('/').one()
50 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 84 def __create_group(self, group_name):
53 85 """
54 86 makes repositories group on filesystem
55 87
56 88 :param repo_name:
57 89 :param parent_id:
58 90 """
59 91
60 92 create_path = os.path.join(self.repos_path, group_name)
61 93 log.debug('creating new group in %s' % create_path)
62 94
63 95 if os.path.isdir(create_path):
64 96 raise Exception('That directory already exists !')
65 97
66 98 os.makedirs(create_path)
67 99
68 100 def __rename_group(self, old, new):
69 101 """
70 102 Renames a group on filesystem
71 103
72 104 :param group_name:
73 105 """
74 106
75 107 if old == new:
76 108 log.debug('skipping group rename')
77 109 return
78 110
79 111 log.debug('renaming repos group from %s to %s' % (old, new))
80 112
81 113 old_path = os.path.join(self.repos_path, old)
82 114 new_path = os.path.join(self.repos_path, new)
83 115
84 116 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
85 117
86 118 if os.path.isdir(new_path):
87 119 raise Exception('Was trying to rename to already '
88 120 'existing dir %s' % new_path)
89 121 shutil.move(old_path, new_path)
90 122
91 123 def __delete_group(self, group):
92 124 """
93 125 Deletes a group from a filesystem
94 126
95 127 :param group: instance of group from database
96 128 """
97 129 paths = group.full_path.split(RepoGroup.url_sep())
98 130 paths = os.sep.join(paths)
99 131
100 132 rm_path = os.path.join(self.repos_path, paths)
101 133 if os.path.isdir(rm_path):
102 134 # delete only if that path really exists
103 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 138 try:
107 139 new_repos_group = RepoGroup()
108 new_repos_group.group_description = form_data['group_description']
109 new_repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
110 new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
140 new_repos_group.group_description = group_description
141 new_repos_group.parent_group = self.__get_repos_group(parent)
142 new_repos_group.group_name = new_repos_group.get_new_name(group_name)
111 143
112 144 self.sa.add(new_repos_group)
113 self.sa.flush()
114 self.__create_group(new_repos_group.group_name)
145 self._create_default_perms(new_repos_group)
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 153 return new_repos_group
117 154 except:
118 155 log.error(traceback.format_exc())
119 156 raise
120 157
121 158 def update(self, repos_group_id, form_data):
122 159
123 160 try:
124 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 185 old_path = repos_group.full_path
126 186
127 187 # change properties
128 188 repos_group.group_description = form_data['group_description']
129 189 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
130 190 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
131 191
132 192 new_path = repos_group.full_path
133 193
134 194 self.sa.add(repos_group)
135 195
136 196 self.__rename_group(old_path, new_path)
137 197
138 198 # we need to get all repositories from this new group and
139 199 # rename them accordingly to new group path
140 200 for r in repos_group.repositories:
141 201 r.repo_name = r.get_new_name(r.just_name)
142 202 self.sa.add(r)
143 203
144 204 return repos_group
145 205 except:
146 206 log.error(traceback.format_exc())
147 207 raise
148 208
149 209 def delete(self, users_group_id):
150 210 try:
151 211 users_group = RepoGroup.get(users_group_id)
152 212 self.sa.delete(users_group)
153 213 self.__delete_group(users_group)
154 214 except:
155 215 log.error(traceback.format_exc())
156 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 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.scm
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Scm model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import time
27 27 import traceback
28 28 import logging
29 29 import cStringIO
30 30
31 31 from vcs import get_backend
32 32 from vcs.exceptions import RepositoryError
33 33 from vcs.utils.lazy import LazyProperty
34 34 from vcs.nodes import FileNode
35 35
36 36 from rhodecode import BACKENDS
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib import safe_str
39 from rhodecode.lib.auth import HasRepoPermissionAny
39 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
40 40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
41 41 action_logger, EmptyChangeset
42 42 from rhodecode.model import BaseModel
43 43 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
44 UserFollowing, UserLog, User
44 UserFollowing, UserLog, User, RepoGroup
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class UserTemp(object):
50 50 def __init__(self, user_id):
51 51 self.user_id = user_id
52 52
53 53 def __repr__(self):
54 54 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
55 55
56 56
57 57 class RepoTemp(object):
58 58 def __init__(self, repo_id):
59 59 self.repo_id = repo_id
60 60
61 61 def __repr__(self):
62 62 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
63 63
64 64
65 65 class CachedRepoList(object):
66 66
67 67 def __init__(self, db_repo_list, repos_path, order_by=None):
68 68 self.db_repo_list = db_repo_list
69 69 self.repos_path = repos_path
70 70 self.order_by = order_by
71 71 self.reversed = (order_by or '').startswith('-')
72 72
73 73 def __len__(self):
74 74 return len(self.db_repo_list)
75 75
76 76 def __repr__(self):
77 77 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
78 78
79 79 def __iter__(self):
80 80 for dbr in self.db_repo_list:
81 81 scmr = dbr.scm_instance_cached
82 82 # check permission at this level
83 if not HasRepoPermissionAny('repository.read', 'repository.write',
84 'repository.admin')(dbr.repo_name,
85 'get repo check'):
83 if not HasRepoPermissionAny(
84 'repository.read', 'repository.write', 'repository.admin'
85 )(dbr.repo_name, 'get repo check'):
86 86 continue
87 87
88 88 if scmr is None:
89 log.error('%s this repository is present in database but it '
90 'cannot be created as an scm instance',
91 dbr.repo_name)
89 log.error(
90 '%s this repository is present in database but it '
91 'cannot be created as an scm instance' % dbr.repo_name
92 )
92 93 continue
93 94
94 95 last_change = scmr.last_change
95 96 tip = h.get_changeset_safe(scmr, 'tip')
96 97
97 98 tmp_d = {}
98 99 tmp_d['name'] = dbr.repo_name
99 100 tmp_d['name_sort'] = tmp_d['name'].lower()
100 101 tmp_d['description'] = dbr.description
101 102 tmp_d['description_sort'] = tmp_d['description']
102 103 tmp_d['last_change'] = last_change
103 104 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
104 105 tmp_d['tip'] = tip.raw_id
105 106 tmp_d['tip_sort'] = tip.revision
106 107 tmp_d['rev'] = tip.revision
107 108 tmp_d['contact'] = dbr.user.full_contact
108 109 tmp_d['contact_sort'] = tmp_d['contact']
109 110 tmp_d['owner_sort'] = tmp_d['contact']
110 111 tmp_d['repo_archives'] = list(scmr._get_archives())
111 112 tmp_d['last_msg'] = tip.message
112 113 tmp_d['author'] = tip.author
113 114 tmp_d['dbrepo'] = dbr.get_dict()
114 115 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
115 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 141 class ScmModel(BaseModel):
119 142 """
120 143 Generic Scm Model
121 144 """
122 145
123 146 def __get_repo(self, instance):
124 147 cls = Repository
125 148 if isinstance(instance, cls):
126 149 return instance
127 150 elif isinstance(instance, int) or str(instance).isdigit():
128 151 return cls.get(instance)
129 152 elif isinstance(instance, basestring):
130 153 return cls.get_by_repo_name(instance)
131 154 elif instance:
132 155 raise Exception('given object must be int, basestr or Instance'
133 156 ' of %s got %s' % (type(cls), type(instance)))
134 157
135 158 @LazyProperty
136 159 def repos_path(self):
137 160 """
138 161 Get's the repositories root path from database
139 162 """
140 163
141 164 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
142 165
143 166 return q.ui_value
144 167
145 168 def repo_scan(self, repos_path=None):
146 169 """
147 170 Listing of repositories in given path. This path should not be a
148 171 repository itself. Return a dictionary of repository objects
149 172
150 173 :param repos_path: path to directory containing repositories
151 174 """
152 175
153 176 if repos_path is None:
154 177 repos_path = self.repos_path
155 178
156 179 log.info('scanning for repositories in %s' % repos_path)
157 180
158 181 baseui = make_ui('db')
159 182 repos = {}
160 183
161 184 for name, path in get_filesystem_repos(repos_path, recursive=True):
162 185
163 186 # name need to be decomposed and put back together using the /
164 187 # since this is internal storage separator for rhodecode
165 188 name = Repository.url_sep().join(name.split(os.sep))
166 189
167 190 try:
168 191 if name in repos:
169 192 raise RepositoryError('Duplicate repository name %s '
170 193 'found in %s' % (name, path))
171 194 else:
172 195
173 196 klass = get_backend(path[0])
174 197
175 198 if path[0] == 'hg' and path[0] in BACKENDS.keys():
176 199 repos[name] = klass(safe_str(path[1]), baseui=baseui)
177 200
178 201 if path[0] == 'git' and path[0] in BACKENDS.keys():
179 202 repos[name] = klass(path[1])
180 203 except OSError:
181 204 continue
182 205
183 206 return repos
184 207
185 208 def get_repos(self, all_repos=None, sort_key=None):
186 209 """
187 210 Get all repos from db and for each repo create it's
188 211 backend instance and fill that backed with information from database
189 212
190 213 :param all_repos: list of repository names as strings
191 214 give specific repositories list, good for filtering
192 215 """
193 216 if all_repos is None:
194 217 all_repos = self.sa.query(Repository)\
195 218 .filter(Repository.group_id == None)\
196 219 .order_by(Repository.repo_name).all()
197 220
198 221 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
199 222 order_by=sort_key)
200 223
201 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 234 def mark_for_invalidation(self, repo_name):
204 235 """Puts cache invalidation task into db for
205 236 further global cache invalidation
206 237
207 238 :param repo_name: this repo that should invalidation take place
208 239 """
209 240 CacheInvalidation.set_invalidate(repo_name)
210 241 CacheInvalidation.set_invalidate(repo_name + "_README")
211 242
212 243 def toggle_following_repo(self, follow_repo_id, user_id):
213 244
214 245 f = self.sa.query(UserFollowing)\
215 246 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
216 247 .filter(UserFollowing.user_id == user_id).scalar()
217 248
218 249 if f is not None:
219 250 try:
220 251 self.sa.delete(f)
221 252 action_logger(UserTemp(user_id),
222 253 'stopped_following_repo',
223 254 RepoTemp(follow_repo_id))
224 255 return
225 256 except:
226 257 log.error(traceback.format_exc())
227 258 raise
228 259
229 260 try:
230 261 f = UserFollowing()
231 262 f.user_id = user_id
232 263 f.follows_repo_id = follow_repo_id
233 264 self.sa.add(f)
234 265
235 266 action_logger(UserTemp(user_id),
236 267 'started_following_repo',
237 268 RepoTemp(follow_repo_id))
238 269 except:
239 270 log.error(traceback.format_exc())
240 271 raise
241 272
242 273 def toggle_following_user(self, follow_user_id, user_id):
243 274 f = self.sa.query(UserFollowing)\
244 275 .filter(UserFollowing.follows_user_id == follow_user_id)\
245 276 .filter(UserFollowing.user_id == user_id).scalar()
246 277
247 278 if f is not None:
248 279 try:
249 280 self.sa.delete(f)
250 281 return
251 282 except:
252 283 log.error(traceback.format_exc())
253 284 raise
254 285
255 286 try:
256 287 f = UserFollowing()
257 288 f.user_id = user_id
258 289 f.follows_user_id = follow_user_id
259 290 self.sa.add(f)
260 291 except:
261 292 log.error(traceback.format_exc())
262 293 raise
263 294
264 295 def is_following_repo(self, repo_name, user_id, cache=False):
265 296 r = self.sa.query(Repository)\
266 297 .filter(Repository.repo_name == repo_name).scalar()
267 298
268 299 f = self.sa.query(UserFollowing)\
269 300 .filter(UserFollowing.follows_repository == r)\
270 301 .filter(UserFollowing.user_id == user_id).scalar()
271 302
272 303 return f is not None
273 304
274 305 def is_following_user(self, username, user_id, cache=False):
275 306 u = User.get_by_username(username)
276 307
277 308 f = self.sa.query(UserFollowing)\
278 309 .filter(UserFollowing.follows_user == u)\
279 310 .filter(UserFollowing.user_id == user_id).scalar()
280 311
281 312 return f is not None
282 313
283 314 def get_followers(self, repo_id):
284 315 if not isinstance(repo_id, int):
285 316 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
286 317
287 318 return self.sa.query(UserFollowing)\
288 319 .filter(UserFollowing.follows_repo_id == repo_id).count()
289 320
290 321 def get_forks(self, repo_id):
291 322 if not isinstance(repo_id, int):
292 323 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
293 324
294 325 return self.sa.query(Repository)\
295 326 .filter(Repository.fork_id == repo_id).count()
296 327
297 328 def mark_as_fork(self, repo, fork, user):
298 329 repo = self.__get_repo(repo)
299 330 fork = self.__get_repo(fork)
300 331 repo.fork = fork
301 332 self.sa.add(repo)
302 333 return repo
303 334
304 335 def pull_changes(self, repo_name, username):
305 336 dbrepo = Repository.get_by_repo_name(repo_name)
306 337 clone_uri = dbrepo.clone_uri
307 338 if not clone_uri:
308 339 raise Exception("This repository doesn't have a clone uri")
309 340
310 341 repo = dbrepo.scm_instance
311 342 try:
312 343 extras = {'ip': '',
313 344 'username': username,
314 345 'action': 'push_remote',
315 346 'repository': repo_name}
316 347
317 348 #inject ui extra param to log this action via push logger
318 349 for k, v in extras.items():
319 350 repo._repo.ui.setconfig('rhodecode_extras', k, v)
320 351
321 352 repo.pull(clone_uri)
322 353 self.mark_for_invalidation(repo_name)
323 354 except:
324 355 log.error(traceback.format_exc())
325 356 raise
326 357
327 358 def commit_change(self, repo, repo_name, cs, user, author, message,
328 359 content, f_path):
329 360
330 361 if repo.alias == 'hg':
331 362 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
332 363 elif repo.alias == 'git':
333 364 from vcs.backends.git import GitInMemoryChangeset as IMC
334 365
335 366 # decoding here will force that we have proper encoded values
336 367 # in any other case this will throw exceptions and deny commit
337 368 content = safe_str(content)
338 369 message = safe_str(message)
339 370 path = safe_str(f_path)
340 371 author = safe_str(author)
341 372 m = IMC(repo)
342 373 m.change(FileNode(path, content))
343 374 tip = m.commit(message=message,
344 375 author=author,
345 376 parents=[cs], branch=cs.branch)
346 377
347 378 new_cs = tip.short_id
348 379 action = 'push_local:%s' % new_cs
349 380
350 381 action_logger(user, action, repo_name)
351 382
352 383 self.mark_for_invalidation(repo_name)
353 384
354 385 def create_node(self, repo, repo_name, cs, user, author, message, content,
355 386 f_path):
356 387 if repo.alias == 'hg':
357 388 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
358 389 elif repo.alias == 'git':
359 390 from vcs.backends.git import GitInMemoryChangeset as IMC
360 391 # decoding here will force that we have proper encoded values
361 392 # in any other case this will throw exceptions and deny commit
362 393
363 394 if isinstance(content, (basestring,)):
364 395 content = safe_str(content)
365 396 elif isinstance(content, (file, cStringIO.OutputType,)):
366 397 content = content.read()
367 398 else:
368 399 raise Exception('Content is of unrecognized type %s' % (
369 400 type(content)
370 401 ))
371 402
372 403 message = safe_str(message)
373 404 path = safe_str(f_path)
374 405 author = safe_str(author)
375 406 m = IMC(repo)
376 407
377 408 if isinstance(cs, EmptyChangeset):
378 409 # Emptychangeset means we we're editing empty repository
379 410 parents = None
380 411 else:
381 412 parents = [cs]
382 413
383 414 m.add(FileNode(path, content=content))
384 415 tip = m.commit(message=message,
385 416 author=author,
386 417 parents=parents, branch=cs.branch)
387 418 new_cs = tip.short_id
388 419 action = 'push_local:%s' % new_cs
389 420
390 421 action_logger(user, action, repo_name)
391 422
392 423 self.mark_for_invalidation(repo_name)
393 424
394 425 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
395 426 """
396 427 recursive walk in root dir and return a set of all path in that dir
397 428 based on repository walk function
398 429
399 430 :param repo_name: name of repository
400 431 :param revision: revision for which to list nodes
401 432 :param root_path: root path to list
402 433 :param flat: return as a list, if False returns a dict with decription
403 434
404 435 """
405 436 _files = list()
406 437 _dirs = list()
407 438 try:
408 439 _repo = self.__get_repo(repo_name)
409 440 changeset = _repo.scm_instance.get_changeset(revision)
410 441 root_path = root_path.lstrip('/')
411 442 for topnode, dirs, files in changeset.walk(root_path):
412 443 for f in files:
413 444 _files.append(f.path if flat else {"name": f.path,
414 445 "type": "file"})
415 446 for d in dirs:
416 447 _dirs.append(d.path if flat else {"name": d.path,
417 448 "type": "dir"})
418 449 except RepositoryError:
419 450 log.debug(traceback.format_exc())
420 451 raise
421 452
422 453 return _dirs, _files
423 454
424 455 def get_unread_journal(self):
425 456 return self.sa.query(UserLog).count()
@@ -1,504 +1,549 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 from pylons import url
30 30 from pylons.i18n.translation import _
31 31
32 32 from rhodecode.lib import safe_unicode
33 33 from rhodecode.lib.caching_query import FromCache
34 34
35 35 from rhodecode.model import BaseModel
36 36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 Notification
38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup
39 39 from rhodecode.lib.exceptions import DefaultUserException, \
40 40 UserOwnsReposException
41 41
42 42 from sqlalchemy.exc import DatabaseError
43 43 from rhodecode.lib import generate_api_key
44 44 from sqlalchemy.orm import joinedload
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 PERM_WEIGHTS = {'repository.none': 0,
50 'repository.read': 1,
51 'repository.write': 3,
52 'repository.admin': 3}
49 PERM_WEIGHTS = {
50 'repository.none': 0,
51 'repository.read': 1,
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 61 class UserModel(BaseModel):
56 62
57 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 70 def get(self, user_id, cache=False):
61 71 user = self.sa.query(User)
62 72 if cache:
63 73 user = user.options(FromCache("sql_cache_short",
64 74 "get_user_%s" % user_id))
65 75 return user.get(user_id)
66 76
67 77 def get_by_username(self, username, cache=False, case_insensitive=False):
68 78
69 79 if case_insensitive:
70 80 user = self.sa.query(User).filter(User.username.ilike(username))
71 81 else:
72 82 user = self.sa.query(User)\
73 83 .filter(User.username == username)
74 84 if cache:
75 85 user = user.options(FromCache("sql_cache_short",
76 86 "get_user_%s" % username))
77 87 return user.scalar()
78 88
79 89 def get_by_api_key(self, api_key, cache=False):
80 90 return User.get_by_api_key(api_key, cache)
81 91
82 92 def create(self, form_data):
83 93 try:
84 94 new_user = User()
85 95 for k, v in form_data.items():
86 96 setattr(new_user, k, v)
87 97
88 98 new_user.api_key = generate_api_key(form_data['username'])
89 99 self.sa.add(new_user)
90 100 return new_user
91 101 except:
92 102 log.error(traceback.format_exc())
93 103 raise
94 104
95 105 def create_or_update(self, username, password, email, name, lastname,
96 106 active=True, admin=False, ldap_dn=None):
97 107 """
98 108 Creates a new instance if not found, or updates current one
99 109
100 110 :param username:
101 111 :param password:
102 112 :param email:
103 113 :param active:
104 114 :param name:
105 115 :param lastname:
106 116 :param active:
107 117 :param admin:
108 118 :param ldap_dn:
109 119 """
110 120
111 121 from rhodecode.lib.auth import get_crypt_password
112 122
113 123 log.debug('Checking for %s account in RhodeCode database' % username)
114 124 user = User.get_by_username(username, case_insensitive=True)
115 125 if user is None:
116 126 log.debug('creating new user %s' % username)
117 127 new_user = User()
118 128 else:
119 129 log.debug('updating user %s' % username)
120 130 new_user = user
121 131
122 132 try:
123 133 new_user.username = username
124 134 new_user.admin = admin
125 135 new_user.password = get_crypt_password(password)
126 136 new_user.api_key = generate_api_key(username)
127 137 new_user.email = email
128 138 new_user.active = active
129 139 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
130 140 new_user.name = name
131 141 new_user.lastname = lastname
132 142 self.sa.add(new_user)
133 143 return new_user
134 144 except (DatabaseError,):
135 145 log.error(traceback.format_exc())
136 146 raise
137 147
138 148 def create_for_container_auth(self, username, attrs):
139 149 """
140 150 Creates the given user if it's not already in the database
141 151
142 152 :param username:
143 153 :param attrs:
144 154 """
145 155 if self.get_by_username(username, case_insensitive=True) is None:
146 156
147 157 # autogenerate email for container account without one
148 158 generate_email = lambda usr: '%s@container_auth.account' % usr
149 159
150 160 try:
151 161 new_user = User()
152 162 new_user.username = username
153 163 new_user.password = None
154 164 new_user.api_key = generate_api_key(username)
155 165 new_user.email = attrs['email']
156 166 new_user.active = attrs.get('active', True)
157 167 new_user.name = attrs['name'] or generate_email(username)
158 168 new_user.lastname = attrs['lastname']
159 169
160 170 self.sa.add(new_user)
161 171 return new_user
162 172 except (DatabaseError,):
163 173 log.error(traceback.format_exc())
164 174 self.sa.rollback()
165 175 raise
166 176 log.debug('User %s already exists. Skipping creation of account'
167 177 ' for container auth.', username)
168 178 return None
169 179
170 180 def create_ldap(self, username, password, user_dn, attrs):
171 181 """
172 182 Checks if user is in database, if not creates this user marked
173 183 as ldap user
174 184
175 185 :param username:
176 186 :param password:
177 187 :param user_dn:
178 188 :param attrs:
179 189 """
180 190 from rhodecode.lib.auth import get_crypt_password
181 191 log.debug('Checking for such ldap account in RhodeCode database')
182 192 if self.get_by_username(username, case_insensitive=True) is None:
183 193
184 194 # autogenerate email for ldap account without one
185 195 generate_email = lambda usr: '%s@ldap.account' % usr
186 196
187 197 try:
188 198 new_user = User()
189 199 username = username.lower()
190 200 # add ldap account always lowercase
191 201 new_user.username = username
192 202 new_user.password = get_crypt_password(password)
193 203 new_user.api_key = generate_api_key(username)
194 204 new_user.email = attrs['email'] or generate_email(username)
195 205 new_user.active = attrs.get('active', True)
196 206 new_user.ldap_dn = safe_unicode(user_dn)
197 207 new_user.name = attrs['name']
198 208 new_user.lastname = attrs['lastname']
199 209
200 210 self.sa.add(new_user)
201 211 return new_user
202 212 except (DatabaseError,):
203 213 log.error(traceback.format_exc())
204 214 self.sa.rollback()
205 215 raise
206 216 log.debug('this %s user exists skipping creation of ldap account',
207 217 username)
208 218 return None
209 219
210 220 def create_registration(self, form_data):
211 221 from rhodecode.model.notification import NotificationModel
212 222
213 223 try:
214 224 new_user = User()
215 225 for k, v in form_data.items():
216 226 if k != 'admin':
217 227 setattr(new_user, k, v)
218 228
219 229 self.sa.add(new_user)
220 230 self.sa.flush()
221 231
222 232 # notification to admins
223 233 subject = _('new user registration')
224 234 body = ('New user registration\n'
225 235 '---------------------\n'
226 236 '- Username: %s\n'
227 237 '- Full Name: %s\n'
228 238 '- Email: %s\n')
229 239 body = body % (new_user.username, new_user.full_name,
230 240 new_user.email)
231 241 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
232 242 kw = {'registered_user_url': edit_url}
233 243 NotificationModel().create(created_by=new_user, subject=subject,
234 244 body=body, recipients=None,
235 245 type_=Notification.TYPE_REGISTRATION,
236 246 email_kwargs=kw)
237 247
238 248 except:
239 249 log.error(traceback.format_exc())
240 250 raise
241 251
242 252 def update(self, user_id, form_data):
243 253 try:
244 254 user = self.get(user_id, cache=False)
245 255 if user.username == 'default':
246 256 raise DefaultUserException(
247 257 _("You can't Edit this user since it's"
248 258 " crucial for entire application"))
249 259
250 260 for k, v in form_data.items():
251 261 if k == 'new_password' and v != '':
252 262 user.password = v
253 263 user.api_key = generate_api_key(user.username)
254 264 else:
255 265 setattr(user, k, v)
256 266
257 267 self.sa.add(user)
258 268 except:
259 269 log.error(traceback.format_exc())
260 270 raise
261 271
262 272 def update_my_account(self, user_id, form_data):
263 273 try:
264 274 user = self.get(user_id, cache=False)
265 275 if user.username == 'default':
266 276 raise DefaultUserException(
267 277 _("You can't Edit this user since it's"
268 278 " crucial for entire application"))
269 279 for k, v in form_data.items():
270 280 if k == 'new_password' and v != '':
271 281 user.password = v
272 282 user.api_key = generate_api_key(user.username)
273 283 else:
274 284 if k not in ['admin', 'active']:
275 285 setattr(user, k, v)
276 286
277 287 self.sa.add(user)
278 288 except:
279 289 log.error(traceback.format_exc())
280 290 raise
281 291
282 292 def delete(self, user):
283 293 user = self.__get_user(user)
284 294
285 295 try:
286 296 if user.username == 'default':
287 297 raise DefaultUserException(
288 298 _("You can't remove this user since it's"
289 299 " crucial for entire application"))
290 300 if user.repositories:
291 301 raise UserOwnsReposException(_('This user still owns %s '
292 302 'repositories and cannot be '
293 303 'removed. Switch owners or '
294 304 'remove those repositories') \
295 305 % user.repositories)
296 306 self.sa.delete(user)
297 307 except:
298 308 log.error(traceback.format_exc())
299 309 raise
300 310
301 311 def reset_password_link(self, data):
302 312 from rhodecode.lib.celerylib import tasks, run_task
303 313 run_task(tasks.send_password_link, data['email'])
304 314
305 315 def reset_password(self, data):
306 316 from rhodecode.lib.celerylib import tasks, run_task
307 317 run_task(tasks.reset_user_password, data['email'])
308 318
309 319 def fill_data(self, auth_user, user_id=None, api_key=None):
310 320 """
311 321 Fetches auth_user by user_id,or api_key if present.
312 322 Fills auth_user attributes with those taken from database.
313 323 Additionally set's is_authenitated if lookup fails
314 324 present in database
315 325
316 326 :param auth_user: instance of user to set attributes
317 327 :param user_id: user id to fetch by
318 328 :param api_key: api key to fetch by
319 329 """
320 330 if user_id is None and api_key is None:
321 331 raise Exception('You need to pass user_id or api_key')
322 332
323 333 try:
324 334 if api_key:
325 335 dbuser = self.get_by_api_key(api_key)
326 336 else:
327 337 dbuser = self.get(user_id)
328 338
329 339 if dbuser is not None and dbuser.active:
330 340 log.debug('filling %s data' % dbuser)
331 341 for k, v in dbuser.get_dict().items():
332 342 setattr(auth_user, k, v)
333 343 else:
334 344 return False
335 345
336 346 except:
337 347 log.error(traceback.format_exc())
338 348 auth_user.is_authenticated = False
339 349 return False
340 350
341 351 return True
342 352
343 353 def fill_perms(self, user):
344 354 """
345 355 Fills user permission attribute with permissions taken from database
346 356 works for permissions given for repositories, and for permissions that
347 357 are granted to groups
348 358
349 359 :param user: user instance to fill his perms
350 360 """
351
352 user.permissions['repositories'] = {}
353 user.permissions['global'] = set()
361 RK = 'repositories'
362 GK = 'repositories_groups'
363 GLOBAL = 'global'
364 user.permissions[RK] = {}
365 user.permissions[GK] = {}
366 user.permissions[GLOBAL] = set()
354 367
355 368 #======================================================================
356 369 # fetch default permissions
357 370 #======================================================================
358 371 default_user = User.get_by_username('default', cache=True)
359 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 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 387 p = 'repository.admin'
371 user.permissions['repositories'][perm.UserRepoToPerm.
372 repository.repo_name] = p
388 user.permissions[RK][r_k] = 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 396 else:
375 397 #==================================================================
376 # set default permissions
398 # set default permissions first for repositories and groups
377 399 #==================================================================
378 400 uid = user.user_id
379 401
380 # default global
402 # default global permissions
381 403 default_global_perms = self.sa.query(UserToPerm)\
382 404 .filter(UserToPerm.user_id == default_user_id)
383 405
384 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 409 # default for repositories
388 for perm in default_perms:
389 if perm.Repository.private and not (perm.Repository.user_id ==
390 uid):
410 for perm in default_repo_perms:
411 r_k = perm.UserRepoToPerm.repository.repo_name
412 if perm.Repository.private and not (perm.Repository.user_id == uid):
391 413 # disable defaults for private repos,
392 414 p = 'repository.none'
393 415 elif perm.Repository.user_id == uid:
394 416 # set admin if owner
395 417 p = 'repository.admin'
396 418 else:
397 419 p = perm.Permission.permission_name
398 420
399 user.permissions['repositories'][perm.UserRepoToPerm.
400 repository.repo_name] = p
421 user.permissions[RK][r_k] = 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 430 # overwrite default with user permissions if any
404 431 #==================================================================
405 432
406 433 # user global
407 434 user_perms = self.sa.query(UserToPerm)\
408 435 .options(joinedload(UserToPerm.permission))\
409 436 .filter(UserToPerm.user_id == uid).all()
410 437
411 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 441 # user repositories
415 user_repo_perms = self.sa.query(UserRepoToPerm, Permission,
416 Repository)\
417 .join((Repository, UserRepoToPerm.repository_id ==
418 Repository.repo_id))\
419 .join((Permission, UserRepoToPerm.permission_id ==
420 Permission.permission_id))\
421 .filter(UserRepoToPerm.user_id == uid).all()
442 user_repo_perms = \
443 self.sa.query(UserRepoToPerm, Permission, Repository)\
444 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
445 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
446 .filter(UserRepoToPerm.user_id == uid)\
447 .all()
422 448
423 449 for perm in user_repo_perms:
424 450 # set admin if owner
451 r_k = perm.UserRepoToPerm.repository.repo_name
425 452 if perm.Repository.user_id == uid:
426 453 p = 'repository.admin'
427 454 else:
428 455 p = perm.Permission.permission_name
429 user.permissions['repositories'][perm.UserRepoToPerm.
430 repository.repo_name] = p
456 user.permissions[RK][r_k] = p
431 457
432 458 #==================================================================
433 459 # check if user is part of groups for this repository and fill in
434 460 # (or replace with higher) permissions
435 461 #==================================================================
436 462
437 463 # users group global
438 464 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
439 465 .options(joinedload(UsersGroupToPerm.permission))\
440 466 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
441 467 UsersGroupMember.users_group_id))\
442 468 .filter(UsersGroupMember.user_id == uid).all()
443 469
444 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 473 # users group repositories
448 user_repo_perms_from_users_groups = self.sa.query(
449 UsersGroupRepoToPerm,
450 Permission, Repository,)\
451 .join((Repository, UsersGroupRepoToPerm.repository_id ==
452 Repository.repo_id))\
453 .join((Permission, UsersGroupRepoToPerm.permission_id ==
454 Permission.permission_id))\
455 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
456 UsersGroupMember.users_group_id))\
457 .filter(UsersGroupMember.user_id == uid).all()
474 user_repo_perms_from_users_groups = \
475 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
476 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
477 .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
478 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
479 .filter(UsersGroupMember.user_id == uid)\
480 .all()
458 481
459 482 for perm in user_repo_perms_from_users_groups:
483 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
460 484 p = perm.Permission.permission_name
461 cur_perm = user.permissions['repositories'][perm.
462 UsersGroupRepoToPerm.
463 repository.repo_name]
485 cur_perm = user.permissions[RK][r_k]
464 486 # overwrite permission only if it's greater than permission
465 487 # given from other sources
466 488 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
467 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
468 repository.repo_name] = p
489 user.permissions[RK][r_k] = 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 510 return user
471 511
472 512 def has_perm(self, user, perm):
473 513 if not isinstance(perm, Permission):
474 514 raise Exception('perm needs to be an instance of Permission class '
475 515 'got %s instead' % type(perm))
476 516
477 517 user = self.__get_user(user)
478 518
479 519 return UserToPerm.query().filter(UserToPerm.user == user)\
480 520 .filter(UserToPerm.permission == perm).scalar() is not None
481 521
482 522 def grant_perm(self, user, perm):
483 if not isinstance(perm, Permission):
484 raise Exception('perm needs to be an instance of Permission class '
485 'got %s instead' % type(perm))
523 """
524 Grant user global permissions
486 525
526 :param user:
527 :param perm:
528 """
487 529 user = self.__get_user(user)
488
530 perm = self.__get_perm(perm)
489 531 new = UserToPerm()
490 532 new.user = user
491 533 new.permission = perm
492 534 self.sa.add(new)
493 535
494 536 def revoke_perm(self, user, perm):
495 if not isinstance(perm, Permission):
496 raise Exception('perm needs to be an instance of Permission class '
497 'got %s instead' % type(perm))
537 """
538 Revoke users global permissions
498 539
540 :param user:
541 :param perm:
542 """
499 543 user = self.__get_user(user)
544 perm = self.__get_perm(perm)
500 545
501 546 obj = UserToPerm.query().filter(UserToPerm.user == user)\
502 547 .filter(UserToPerm.permission == perm).scalar()
503 548 if obj:
504 549 self.sa.delete(obj)
@@ -1,151 +1,160 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.users_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users group model for RhodeCode
7 7
8 8 :created_on: Oct 1, 2011
9 9 :author: nvinot
10 10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import logging
28 28 import traceback
29 29
30 30 from rhodecode.model import BaseModel
31 31 from rhodecode.model.db import UsersGroupMember, UsersGroup,\
32 32 UsersGroupRepoToPerm, Permission, UsersGroupToPerm
33 33 from rhodecode.lib.exceptions import UsersGroupsAssignedException
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 class UsersGroupModel(BaseModel):
39 39
40 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 48 def get(self, users_group_id, cache=False):
44 49 return UsersGroup.get(users_group_id)
45 50
46 51 def get_by_name(self, name, cache=False, case_insensitive=False):
47 52 return UsersGroup.get_by_group_name(name, cache, case_insensitive)
48 53
49 54 def create(self, name, active=True):
50 55 try:
51 56 new = UsersGroup()
52 57 new.users_group_name = name
53 58 new.users_group_active = active
54 59 self.sa.add(new)
55 60 return new
56 61 except:
57 62 log.error(traceback.format_exc())
58 63 raise
59 64
60 65 def update(self, users_group, form_data):
61 66
62 67 try:
63 68 users_group = self.__get_users_group(users_group)
64 69
65 70 for k, v in form_data.items():
66 71 if k == 'users_group_members':
67 72 users_group.members = []
68 73 self.sa.flush()
69 74 members_list = []
70 75 if v:
71 76 v = [v] if isinstance(v, basestring) else v
72 77 for u_id in set(v):
73 78 member = UsersGroupMember(users_group.users_group_id, u_id)
74 79 members_list.append(member)
75 80 setattr(users_group, 'members', members_list)
76 81 setattr(users_group, k, v)
77 82
78 83 self.sa.add(users_group)
79 84 except:
80 85 log.error(traceback.format_exc())
81 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 97 try:
85 98 users_group = self.__get_users_group(users_group)
86 99
87 100 # check if this group is not assigned to repo
88 101 assigned_groups = UsersGroupRepoToPerm.query()\
89 102 .filter(UsersGroupRepoToPerm.users_group == users_group).all()
90 103
91 if assigned_groups:
104 if assigned_groups and force is False:
92 105 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
93 106 assigned_groups)
94 107
95 108 self.sa.delete(users_group)
96 109 except:
97 110 log.error(traceback.format_exc())
98 111 raise
99 112
100 113 def add_user_to_group(self, users_group, user):
101 114 for m in users_group.members:
102 115 u = m.user
103 116 if u.user_id == user.user_id:
104 117 return m
105 118
106 119 try:
107 120 users_group_member = UsersGroupMember()
108 121 users_group_member.user = user
109 122 users_group_member.users_group = users_group
110 123
111 124 users_group.members.append(users_group_member)
112 125 user.group_member.append(users_group_member)
113 126
114 127 self.sa.add(users_group_member)
115 128 return users_group_member
116 129 except:
117 130 log.error(traceback.format_exc())
118 131 raise
119 132
120 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 134 users_group = self.__get_users_group(users_group)
135 perm = self.__get_perm(perm)
125 136
126 137 return UsersGroupToPerm.query()\
127 138 .filter(UsersGroupToPerm.users_group == users_group)\
128 139 .filter(UsersGroupToPerm.permission == perm).scalar() is not None
129 140
130 141 def grant_perm(self, users_group, perm):
131 142 if not isinstance(perm, Permission):
132 143 raise Exception('perm needs to be an instance of Permission class')
133 144
134 145 users_group = self.__get_users_group(users_group)
135 146
136 147 new = UsersGroupToPerm()
137 148 new.users_group = users_group
138 149 new.permission = perm
139 150 self.sa.add(new)
140 151
141 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 153 users_group = self.__get_users_group(users_group)
154 perm = self.__get_perm(perm)
146 155
147 156 obj = UsersGroupToPerm.query()\
148 157 .filter(UsersGroupToPerm.users_group == users_group)\
149 158 .filter(UsersGroupToPerm.permission == perm).scalar()
150 159 if obj:
151 160 self.sa.delete(obj)
@@ -1,64 +1,73 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
6 6 </%def>
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 9 &raquo;
10 10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 11 &raquo;
12 12 ${_('edit repos group')} "${c.repos_group.name}"
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('admin')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="group_name">${_('Group name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('group_name',class_='medium')}
36 36 </div>
37 37 </div>
38 38
39 39 <div class="field">
40 40 <div class="label label-textarea">
41 41 <label for="group_description">${_('Description')}:</label>
42 42 </div>
43 43 <div class="textarea text-area editor">
44 44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 45 </div>
46 46 </div>
47 47
48 48 <div class="field">
49 49 <div class="label">
50 50 <label for="group_parent_id">${_('Group parent')}:</label>
51 51 </div>
52 52 <div class="input">
53 53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
54 54 </div>
55 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 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 68 </div>
60 69 </div>
61 70 </div>
62 71 ${h.end_form()}
63 72 </div>
64 73 </%def>
@@ -1,195 +1,197 b''
1 1 <%page args="parent" />
2 2 <div class="box">
3 3 <!-- box / title -->
4 4 <div class="title">
5 5 <h5>
6 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 7 </h5>
8 8 %if c.rhodecode_user.username != 'default':
9 9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 10 <ul class="links">
11 11 <li>
12 12 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
13 13 </li>
14 14 </ul>
15 15 %endif
16 16 %endif
17 17 </div>
18 18 <!-- end box / title -->
19 19 <div class="table">
20 20 % if c.groups:
21 21 <div id='groups_list_wrap' class="yui-skin-sam">
22 22 <table id="groups_list">
23 23 <thead>
24 24 <tr>
25 25 <th class="left"><a href="#">${_('Group name')}</a></th>
26 26 <th class="left"><a href="#">${_('Description')}</a></th>
27 27 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
28 28 </tr>
29 29 </thead>
30 30
31 31 ## REPO GROUPS
32 32 % for gr in c.groups:
33 33 <tr>
34 34 <td>
35 35 <div style="white-space: nowrap">
36 36 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
37 37 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
38 38 </div>
39 39 </td>
40 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 44 </tr>
43 45 % endfor
44 46
45 47 </table>
46 48 </div>
47 49 <div style="height: 20px"></div>
48 50 % endif
49 51 <div id="welcome" style="display:none;text-align:center">
50 52 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
51 53 </div>
52 54 <div id='repos_list_wrap' class="yui-skin-sam">
53 55 <%cnt=0%>
54 56 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
55 57
56 58 <table id="repos_list">
57 59 <thead>
58 60 <tr>
59 61 <th class="left"></th>
60 62 <th class="left">${_('Name')}</th>
61 63 <th class="left">${_('Description')}</th>
62 64 <th class="left">${_('Last change')}</th>
63 65 <th class="left">${_('Tip')}</th>
64 66 <th class="left">${_('Owner')}</th>
65 67 <th class="left">${_('RSS')}</th>
66 68 <th class="left">${_('Atom')}</th>
67 69 </tr>
68 70 </thead>
69 71 <tbody>
70 72 %for cnt,repo in enumerate(c.repos_list,1):
71 73 <tr class="parity${cnt%2}">
72 74 ##QUICK MENU
73 75 <td class="quick_repo_menu">
74 76 ${dt.quick_menu(repo['name'])}
75 77 </td>
76 78 ##REPO NAME AND ICONS
77 79 <td class="reponame">
78 80 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
79 81 </td>
80 82 ##DESCRIPTION
81 83 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
82 84 ${h.truncate(repo['description'],60)}</span>
83 85 </td>
84 86 ##LAST CHANGE DATE
85 87 <td>
86 88 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
87 89 </td>
88 90 ##LAST REVISION
89 91 <td>
90 92 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
91 93 </td>
92 94 ##
93 95 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
94 96 <td>
95 97 %if c.rhodecode_user.username != 'default':
96 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 99 %else:
98 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 101 %endif:
100 102 </td>
101 103 <td>
102 104 %if c.rhodecode_user.username != 'default':
103 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 106 %else:
105 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 108 %endif:
107 109 </td>
108 110 </tr>
109 111 %endfor
110 112 </tbody>
111 113 </table>
112 114 </div>
113 115 </div>
114 116 </div>
115 117 <script>
116 118 YUD.get('repo_count').innerHTML = ${cnt};
117 119 var func = function(node){
118 120 return node.parentNode.parentNode.parentNode.parentNode;
119 121 }
120 122
121 123
122 124 // groups table sorting
123 125 var myColumnDefs = [
124 126 {key:"name",label:"${_('Group Name')}",sortable:true,
125 127 sortOptions: { sortFunction: groupNameSort }},
126 128 {key:"desc",label:"${_('Description')}",sortable:true},
127 129 ];
128 130
129 131 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
130 132
131 133 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
132 134 myDataSource.responseSchema = {
133 135 fields: [
134 136 {key:"name"},
135 137 {key:"desc"},
136 138 ]
137 139 };
138 140
139 141 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
140 142 {
141 143 sortedBy:{key:"name",dir:"asc"},
142 144 MSG_SORTASC:"${_('Click to sort ascending')}",
143 145 MSG_SORTDESC:"${_('Click to sort descending')}"
144 146 }
145 147 );
146 148
147 149 // main table sorting
148 150 var myColumnDefs = [
149 151 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
150 152 {key:"name",label:"${_('Name')}",sortable:true,
151 153 sortOptions: { sortFunction: nameSort }},
152 154 {key:"desc",label:"${_('Description')}",sortable:true},
153 155 {key:"last_change",label:"${_('Last Change')}",sortable:true,
154 156 sortOptions: { sortFunction: ageSort }},
155 157 {key:"tip",label:"${_('Tip')}",sortable:true,
156 158 sortOptions: { sortFunction: revisionSort }},
157 159 {key:"owner",label:"${_('Owner')}",sortable:true},
158 160 {key:"rss",label:"",sortable:false},
159 161 {key:"atom",label:"",sortable:false},
160 162 ];
161 163
162 164 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
163 165
164 166 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
165 167
166 168 myDataSource.responseSchema = {
167 169 fields: [
168 170 {key:"menu"},
169 171 {key:"name"},
170 172 {key:"desc"},
171 173 {key:"last_change"},
172 174 {key:"tip"},
173 175 {key:"owner"},
174 176 {key:"rss"},
175 177 {key:"atom"},
176 178 ]
177 179 };
178 180
179 181 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
180 182 {
181 183 sortedBy:{key:"name",dir:"asc"},
182 184 MSG_SORTASC:"${_('Click to sort ascending')}",
183 185 MSG_SORTDESC:"${_('Click to sort descending')}",
184 186 MSG_EMPTY:"${_('No records found.')}",
185 187 MSG_ERROR:"${_('Data error.')}",
186 188 MSG_LOADING:"${_('Loading...')}",
187 189 }
188 190 );
189 191 myDataTable.subscribe('postRenderEvent',function(oArgs) {
190 192 tooltip_activate();
191 193 quick_repo_menu();
192 194 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
193 195 });
194 196
195 197 </script>
@@ -1,404 +1,555 b''
1 1 import os
2 2 import unittest
3 3 from rhodecode.tests import *
4 4
5 5 from rhodecode.model.repos_group import ReposGroupModel
6 6 from rhodecode.model.repo import RepoModel
7 7 from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
8 8 UsersGroup, UsersGroupMember, Permission
9 9 from sqlalchemy.exc import IntegrityError
10 10 from rhodecode.model.user import UserModel
11 11
12 12 from rhodecode.model.meta import Session
13 13 from rhodecode.model.notification import NotificationModel
14 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 30 class TestReposGroups(unittest.TestCase):
17 31
18 32 def setUp(self):
19 self.g1 = self.__make_group('test1', skip_if_exists=True)
20 self.g2 = self.__make_group('test2', skip_if_exists=True)
21 self.g3 = self.__make_group('test3', skip_if_exists=True)
33 self.g1 = _make_group('test1', skip_if_exists=True)
34 self.g2 = _make_group('test2', skip_if_exists=True)
35 self.g3 = _make_group('test3', skip_if_exists=True)
22 36
23 37 def tearDown(self):
24 38 print 'out'
25 39
26 40 def __check_path(self, *path):
27 41 path = [TESTS_TMP_PATH] + list(path)
28 42 path = os.path.join(*path)
29 43 return os.path.isdir(path)
30 44
31 45 def _check_folders(self):
32 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 48 def __delete_group(self, id_):
49 49 ReposGroupModel().delete(id_)
50 50
51
52 51 def __update_group(self, id_, path, desc='desc', parent_id=None):
53 52 form_data = dict(group_name=path,
54 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 58 gr = ReposGroupModel().update(id_, form_data)
58 59 return gr
59 60
60 61 def test_create_group(self):
61 g = self.__make_group('newGroup')
62 g = _make_group('newGroup')
62 63 self.assertEqual(g.full_path, 'newGroup')
63 64
64 65 self.assertTrue(self.__check_path('newGroup'))
65 66
66
67 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 69 Session.rollback()
70 70
71 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 73 self.assertEqual(sg1.parent_group, self.g1)
74 74 self.assertEqual(sg1.full_path, 'test1/sub1')
75 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 78 self.assertEqual(ssg1.parent_group, sg1)
79 79 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
80 80 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
81 81
82
83 82 def test_remove_group(self):
84 sg1 = self.__make_group('deleteme')
83 sg1 = _make_group('deleteme')
85 84 self.__delete_group(sg1.group_id)
86 85
87 86 self.assertEqual(RepoGroup.get(sg1.group_id), None)
88 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 90 self.__delete_group(sg1.group_id)
92 91
93 92 self.assertEqual(RepoGroup.get(sg1.group_id), None)
94 93 self.assertFalse(self.__check_path('test1', 'deteteme'))
95 94
96
97 95 def test_rename_single_group(self):
98 sg1 = self.__make_group('initial')
96 sg1 = _make_group('initial')
99 97
100 98 new_sg1 = self.__update_group(sg1.group_id, 'after')
101 99 self.assertTrue(self.__check_path('after'))
102 100 self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
103 101
104
105 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 106 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
110 107 self.assertTrue(self.__check_path('test1', 'after'))
111 108 self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
112 109
113
114 110 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
115 111 self.assertTrue(self.__check_path('test3', 'after'))
116 112 self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
117 113
118
119 114 new_sg1 = self.__update_group(sg1.group_id, 'hello')
120 115 self.assertTrue(self.__check_path('hello'))
121 116
122 117 self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
123 118
124
125
126 119 def test_subgrouping_with_repo(self):
127 120
128 g1 = self.__make_group('g1')
129 g2 = self.__make_group('g2')
121 g1 = _make_group('g1')
122 g2 = _make_group('g2')
130 123
131 124 # create new repo
132 125 form_data = dict(repo_name='john',
133 126 repo_name_full='john',
134 127 fork_name=None,
135 128 description=None,
136 129 repo_group=None,
137 130 private=False,
138 131 repo_type='hg',
139 132 clone_uri=None)
140 133 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
141 134 r = RepoModel().create(form_data, cur_user)
142 135
143 136 self.assertEqual(r.repo_name, 'john')
144 137
145 138 # put repo into group
146 139 form_data = form_data
147 140 form_data['repo_group'] = g1.group_id
148 141 form_data['perms_new'] = []
149 142 form_data['perms_updates'] = []
150 143 RepoModel().update(r.repo_name, form_data)
151 144 self.assertEqual(r.repo_name, 'g1/john')
152 145
153
154 146 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
155 147 self.assertTrue(self.__check_path('g2', 'g1'))
156 148
157 149 # test repo
158 150 self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
159 151
152
160 153 class TestUser(unittest.TestCase):
161 154 def __init__(self, methodName='runTest'):
162 155 Session.remove()
163 156 super(TestUser, self).__init__(methodName=methodName)
164 157
165 158 def test_create_and_remove(self):
166 159 usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe',
167 160 email=u'u232@rhodecode.org',
168 161 name=u'u1', lastname=u'u1')
169 162 Session.commit()
170 163 self.assertEqual(User.get_by_username(u'test_user'), usr)
171 164
172 165 # make users group
173 166 users_group = UsersGroupModel().create('some_example_group')
174 167 Session.commit()
175 168
176 169 UsersGroupModel().add_user_to_group(users_group, usr)
177 170 Session.commit()
178 171
179 172 self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
180 173 self.assertEqual(UsersGroupMember.query().count(), 1)
181 174 UserModel().delete(usr.user_id)
182 175 Session.commit()
183 176
184 177 self.assertEqual(UsersGroupMember.query().all(), [])
185 178
186 179
187 180 class TestNotifications(unittest.TestCase):
188 181
189 182 def __init__(self, methodName='runTest'):
190 183 Session.remove()
191 184 self.u1 = UserModel().create_or_update(username=u'u1',
192 185 password=u'qweqwe',
193 186 email=u'u1@rhodecode.org',
194 187 name=u'u1', lastname=u'u1')
195 188 Session.commit()
196 189 self.u1 = self.u1.user_id
197 190
198 191 self.u2 = UserModel().create_or_update(username=u'u2',
199 192 password=u'qweqwe',
200 193 email=u'u2@rhodecode.org',
201 194 name=u'u2', lastname=u'u3')
202 195 Session.commit()
203 196 self.u2 = self.u2.user_id
204 197
205 198 self.u3 = UserModel().create_or_update(username=u'u3',
206 199 password=u'qweqwe',
207 200 email=u'u3@rhodecode.org',
208 201 name=u'u3', lastname=u'u3')
209 202 Session.commit()
210 203 self.u3 = self.u3.user_id
211 204
212 205 super(TestNotifications, self).__init__(methodName=methodName)
213 206
214 207 def _clean_notifications(self):
215 208 for n in Notification.query().all():
216 209 Session.delete(n)
217 210
218 211 Session.commit()
219 212 self.assertEqual(Notification.query().all(), [])
220 213
221 214 def tearDown(self):
222 215 self._clean_notifications()
223 216
224 217 def test_create_notification(self):
225 218 self.assertEqual([], Notification.query().all())
226 219 self.assertEqual([], UserNotification.query().all())
227 220
228 221 usrs = [self.u1, self.u2]
229 222 notification = NotificationModel().create(created_by=self.u1,
230 223 subject=u'subj', body=u'hi there',
231 224 recipients=usrs)
232 225 Session.commit()
233 226 u1 = User.get(self.u1)
234 227 u2 = User.get(self.u2)
235 228 u3 = User.get(self.u3)
236 229 notifications = Notification.query().all()
237 230 self.assertEqual(len(notifications), 1)
238 231
239 232 unotification = UserNotification.query()\
240 233 .filter(UserNotification.notification == notification).all()
241 234
242 235 self.assertEqual(notifications[0].recipients, [u1, u2])
243 236 self.assertEqual(notification.notification_id,
244 237 notifications[0].notification_id)
245 238 self.assertEqual(len(unotification), len(usrs))
246 239 self.assertEqual([x.user.user_id for x in unotification], usrs)
247 240
248
249 241 def test_user_notifications(self):
250 242 self.assertEqual([], Notification.query().all())
251 243 self.assertEqual([], UserNotification.query().all())
252 244
253 245 notification1 = NotificationModel().create(created_by=self.u1,
254 246 subject=u'subj', body=u'hi there1',
255 247 recipients=[self.u3])
256 248 Session.commit()
257 249 notification2 = NotificationModel().create(created_by=self.u1,
258 250 subject=u'subj', body=u'hi there2',
259 251 recipients=[self.u3])
260 252 Session.commit()
261 253 u3 = Session.query(User).get(self.u3)
262 254
263 255 self.assertEqual(sorted([x.notification for x in u3.notifications]),
264 256 sorted([notification2, notification1]))
265 257
266 258 def test_delete_notifications(self):
267 259 self.assertEqual([], Notification.query().all())
268 260 self.assertEqual([], UserNotification.query().all())
269 261
270 262 notification = NotificationModel().create(created_by=self.u1,
271 263 subject=u'title', body=u'hi there3',
272 264 recipients=[self.u3, self.u1, self.u2])
273 265 Session.commit()
274 266 notifications = Notification.query().all()
275 267 self.assertTrue(notification in notifications)
276 268
277 269 Notification.delete(notification.notification_id)
278 270 Session.commit()
279 271
280 272 notifications = Notification.query().all()
281 273 self.assertFalse(notification in notifications)
282 274
283 275 un = UserNotification.query().filter(UserNotification.notification
284 276 == notification).all()
285 277 self.assertEqual(un, [])
286 278
287
288 279 def test_delete_association(self):
289 280
290 281 self.assertEqual([], Notification.query().all())
291 282 self.assertEqual([], UserNotification.query().all())
292 283
293 284 notification = NotificationModel().create(created_by=self.u1,
294 285 subject=u'title', body=u'hi there3',
295 286 recipients=[self.u3, self.u1, self.u2])
296 287 Session.commit()
297 288
298 289 unotification = UserNotification.query()\
299 290 .filter(UserNotification.notification ==
300 291 notification)\
301 292 .filter(UserNotification.user_id == self.u3)\
302 293 .scalar()
303 294
304 295 self.assertEqual(unotification.user_id, self.u3)
305 296
306 297 NotificationModel().delete(self.u3,
307 298 notification.notification_id)
308 299 Session.commit()
309 300
310 301 u3notification = UserNotification.query()\
311 302 .filter(UserNotification.notification ==
312 303 notification)\
313 304 .filter(UserNotification.user_id == self.u3)\
314 305 .scalar()
315 306
316 307 self.assertEqual(u3notification, None)
317 308
318 309 # notification object is still there
319 310 self.assertEqual(Notification.query().all(), [notification])
320 311
321 312 #u1 and u2 still have assignments
322 313 u1notification = UserNotification.query()\
323 314 .filter(UserNotification.notification ==
324 315 notification)\
325 316 .filter(UserNotification.user_id == self.u1)\
326 317 .scalar()
327 318 self.assertNotEqual(u1notification, None)
328 319 u2notification = UserNotification.query()\
329 320 .filter(UserNotification.notification ==
330 321 notification)\
331 322 .filter(UserNotification.user_id == self.u2)\
332 323 .scalar()
333 324 self.assertNotEqual(u2notification, None)
334 325
335 326 def test_notification_counter(self):
336 327 self._clean_notifications()
337 328 self.assertEqual([], Notification.query().all())
338 329 self.assertEqual([], UserNotification.query().all())
339 330
340 331 NotificationModel().create(created_by=self.u1,
341 332 subject=u'title', body=u'hi there_delete',
342 333 recipients=[self.u3, self.u1])
343 334 Session.commit()
344 335
345 336 self.assertEqual(NotificationModel()
346 337 .get_unread_cnt_for_user(self.u1), 1)
347 338 self.assertEqual(NotificationModel()
348 339 .get_unread_cnt_for_user(self.u2), 0)
349 340 self.assertEqual(NotificationModel()
350 341 .get_unread_cnt_for_user(self.u3), 1)
351 342
352 343 notification = NotificationModel().create(created_by=self.u1,
353 344 subject=u'title', body=u'hi there3',
354 345 recipients=[self.u3, self.u1, self.u2])
355 346 Session.commit()
356 347
357 348 self.assertEqual(NotificationModel()
358 349 .get_unread_cnt_for_user(self.u1), 2)
359 350 self.assertEqual(NotificationModel()
360 351 .get_unread_cnt_for_user(self.u2), 1)
361 352 self.assertEqual(NotificationModel()
362 353 .get_unread_cnt_for_user(self.u3), 2)
363 354
355
364 356 class TestUsers(unittest.TestCase):
365 357
366 358 def __init__(self, methodName='runTest'):
367 359 super(TestUsers, self).__init__(methodName=methodName)
368 360
369 361 def setUp(self):
370 362 self.u1 = UserModel().create_or_update(username=u'u1',
371 363 password=u'qweqwe',
372 364 email=u'u1@rhodecode.org',
373 365 name=u'u1', lastname=u'u1')
374 366
375 367 def tearDown(self):
376 368 perm = Permission.query().all()
377 369 for p in perm:
378 370 UserModel().revoke_perm(self.u1, p)
379 371
380 372 UserModel().delete(self.u1)
381 373 Session.commit()
382 374
383 375 def test_add_perm(self):
384 376 perm = Permission.query().all()[0]
385 377 UserModel().grant_perm(self.u1, perm)
386 378 Session.commit()
387 379 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
388 380
389 381 def test_has_perm(self):
390 382 perm = Permission.query().all()
391 383 for p in perm:
392 384 has_p = UserModel().has_perm(self.u1, p)
393 385 self.assertEqual(False, has_p)
394 386
395 387 def test_revoke_perm(self):
396 388 perm = Permission.query().all()[0]
397 389 UserModel().grant_perm(self.u1, perm)
398 390 Session.commit()
399 391 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
400 392
401 393 #revoke
402 394 UserModel().revoke_perm(self.u1, perm)
403 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 3 # RhodeCode - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 10 pdebug = false
11 11 ################################################################################
12 12 ## Uncomment and replace with the address which should receive ##
13 13 ## any error reports after application crash ##
14 14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 15 ################################################################################
16 16 #email_to = admin@localhost
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 20
21 21 #smtp_server = mail.server.com
22 22 #smtp_username =
23 23 #smtp_password =
24 24 #smtp_port =
25 25 #smtp_use_tls = false
26 26 #smtp_use_ssl = true
27 27
28 28 [server:main]
29 29 ##nr of threads to spawn
30 30 threadpool_workers = 5
31 31
32 32 ##max request before thread respawn
33 33 threadpool_max_requests = 2
34 34
35 35 ##option to use threads of process
36 36 use_threadpool = true
37 37
38 38 use = egg:Paste#http
39 39 host = 127.0.0.1
40 40 port = 5000
41 41
42 42 [app:main]
43 43 use = egg:rhodecode
44 44 full_stack = true
45 45 static_files = true
46 46 lang=en
47 47 cache_dir = /tmp/data
48 48 index_dir = /tmp/index
49 49 app_instance_uuid = develop-test
50 50 cut_off_limit = 256000
51 51 force_https = false
52 52 commit_parse_limit = 25
53 53 use_gravatar = true
54 54 container_auth_enabled = false
55 55 proxypass_auth_enabled = false
56 56
57 57 ####################################
58 58 ### CELERY CONFIG ####
59 59 ####################################
60 60 use_celery = false
61 61 broker.host = localhost
62 62 broker.vhost = rabbitmqhost
63 63 broker.port = 5672
64 64 broker.user = rabbitmq
65 65 broker.password = qweqwe
66 66
67 67 celery.imports = rhodecode.lib.celerylib.tasks
68 68
69 69 celery.result.backend = amqp
70 70 celery.result.dburi = amqp://
71 71 celery.result.serialier = json
72 72
73 73 #celery.send.task.error.emails = true
74 74 #celery.amqp.task.result.expires = 18000
75 75
76 76 celeryd.concurrency = 2
77 77 #celeryd.log.file = celeryd.log
78 78 celeryd.log.level = debug
79 79 celeryd.max.tasks.per.child = 1
80 80
81 81 #tasks will never be sent to the queue, but executed locally instead.
82 82 celery.always.eager = false
83 83
84 84 ####################################
85 85 ### BEAKER CACHE ####
86 86 ####################################
87 87 beaker.cache.data_dir=/tmp/data/cache/data
88 88 beaker.cache.lock_dir=/tmp/data/cache/lock
89 89 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
90 90
91 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 93 beaker.cache.super_short_term.key_length = 256
94 94
95 95 beaker.cache.short_term.type=memory
96 96 beaker.cache.short_term.expire=60
97 97 beaker.cache.short_term.key_length = 256
98 98
99 99 beaker.cache.long_term.type=memory
100 100 beaker.cache.long_term.expire=36000
101 101 beaker.cache.long_term.key_length = 256
102 102
103 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 105 beaker.cache.sql_cache_short.key_length = 256
106 106
107 107 beaker.cache.sql_cache_med.type=memory
108 108 beaker.cache.sql_cache_med.expire=360
109 109 beaker.cache.sql_cache_med.key_length = 256
110 110
111 111 beaker.cache.sql_cache_long.type=file
112 112 beaker.cache.sql_cache_long.expire=3600
113 113 beaker.cache.sql_cache_long.key_length = 256
114 114
115 115 ####################################
116 116 ### BEAKER SESSION ####
117 117 ####################################
118 118 ## Type of storage used for the session, current types are
119 119 ## dbm, file, memcached, database, and memory.
120 120 ## The storage uses the Container API
121 121 ##that is also used by the cache system.
122 122 beaker.session.type = file
123 123
124 124 beaker.session.key = rhodecode
125 125 beaker.session.secret = g654dcno0-9873jhgfreyu
126 126 beaker.session.timeout = 36000
127 127
128 128 ##auto save the session to not to use .save()
129 129 beaker.session.auto = False
130 130
131 131 ##true exire at browser close
132 132 #beaker.session.cookie_expires = 3600
133 133
134 134
135 135 ################################################################################
136 136 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
137 137 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
138 138 ## execute malicious code after an exception is raised. ##
139 139 ################################################################################
140 140 #set debug = false
141 141
142 142 ##################################
143 143 ### LOGVIEW CONFIG ###
144 144 ##################################
145 145 logview.sqlalchemy = #faa
146 146 logview.pylons.templating = #bfb
147 147 logview.pylons.util = #eee
148 148
149 149 #########################################################
150 150 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
151 151 #########################################################
152 152 sqlalchemy.db1.url = sqlite:///%(here)s/test.db
153 153 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
154 154 #sqlalchemy.db1.echo = false
155 155 #sqlalchemy.db1.pool_recycle = 3600
156 156 sqlalchemy.convert_unicode = true
157 157
158 158 ################################
159 159 ### LOGGING CONFIGURATION ####
160 160 ################################
161 161 [loggers]
162 162 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
163 163
164 164 [handlers]
165 165 keys = console
166 166
167 167 [formatters]
168 168 keys = generic, color_formatter
169 169
170 170 #############
171 171 ## LOGGERS ##
172 172 #############
173 173 [logger_root]
174 174 level = ERROR
175 175 handlers = console
176 176
177 177 [logger_routes]
178 178 level = ERROR
179 179 handlers =
180 180 qualname = routes.middleware
181 181 # "level = DEBUG" logs the route matched and routing variables.
182 182 propagate = 1
183 183
184 184 [logger_beaker]
185 185 level = DEBUG
186 186 handlers =
187 187 qualname = beaker.container
188 188 propagate = 1
189 189
190 190 [logger_templates]
191 191 level = INFO
192 192 handlers =
193 193 qualname = pylons.templating
194 194 propagate = 1
195 195
196 196 [logger_rhodecode]
197 197 level = ERROR
198 198 handlers =
199 199 qualname = rhodecode
200 200 propagate = 1
201 201
202 202 [logger_sqlalchemy]
203 203 level = ERROR
204 204 handlers = console
205 205 qualname = sqlalchemy.engine
206 206 propagate = 0
207 207
208 208 ##############
209 209 ## HANDLERS ##
210 210 ##############
211 211
212 212 [handler_console]
213 213 class = StreamHandler
214 214 args = (sys.stderr,)
215 215 level = NOTSET
216 216 formatter = generic
217 217
218 218 ################
219 219 ## FORMATTERS ##
220 220 ################
221 221
222 222 [formatter_generic]
223 223 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
224 224 datefmt = %Y-%m-%d %H:%M:%S
225 225
226 226 [formatter_color_formatter]
227 227 class=rhodecode.lib.colored_formatter.ColorFormatter
228 228 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
229 229 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now