diff --git a/development.ini b/development.ini --- a/development.ini +++ b/development.ini @@ -24,6 +24,8 @@ pdebug = false #smtp_port = #smtp_use_tls = false #smtp_use_ssl = true +# Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.) +#smtp_auth = [server:main] ##nr of threads to spawn diff --git a/docs/api/api.rst b/docs/api/api.rst --- a/docs/api/api.rst +++ b/docs/api/api.rst @@ -7,7 +7,7 @@ API Starting from RhodeCode version 1.2 a simple API was implemented. There's a single schema for calling all api methods. API is implemented -with JSON protocol both ways. An url to send API request in RhodeCode is +with JSON protocol both ways. An url to send API request in RhodeCode is /_admin/api @@ -22,90 +22,341 @@ All clients need to send JSON data in su Example call for autopulling remotes repos using curl:: curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}' -Simply provide +Simply provide - *api_key* for access and permission validation. - *method* is name of method to call - *args* is an key:value list of arguments to pass to method - + .. note:: - - api_key can be found in your user account page - - + + api_key can be found in your user account page + + RhodeCode API will return always a JSON formatted answer:: - + { - "result": "", + "result": "", "error": null } All responses from API will be `HTTP/1.0 200 OK`, if there's an error while -calling api *error* key from response will contain failure description +calling api *error* key from response will contain failure description and result will be null. API METHODS +++++++++++ - + pull ---- -Pulls given repo from remote location. Can be used to automatically keep -remote repos up to date. This command can be executed only using api_key -belonging to user with admin rights - -INPUT:: - - api_key:"" - method: "pull" - args: {"repo":} - -OUTPUT:: - - result:"Pulled from " - error:null - - -create_user ------------ - -Creates new user in RhodeCode. This command can be executed only using api_key +Pulls given repo from remote location. Can be used to automatically keep +remote repos up to date. This command can be executed only using api_key belonging to user with admin rights INPUT:: - api_key:"" - method: "create_user" - args: {"username": "", - "password": "", - "active": "", - "admin": "", - "name": "", - "lastname": "", - "email": ""} + api_key : "" + method : "pull" + args : { + "repo" : "" + } + +OUTPUT:: + + result : "Pulled from " + error : null + + +get_users +--------- + +Lists all existing users. This command can be executed only using api_key +belonging to user with admin rights. + +INPUT:: + + api_key : "" + method : "get_users" + args : { } + +OUTPUT:: + + result: [ + { + "id" : "", + "username" : "", + "firstname": "", + "lastname" : "", + "email" : "", + "active" : "", + "admin" :  "", + "ldap" : "" + }, + … + ] + error: null + +create_user +----------- + +Creates new user in RhodeCode. This command can be executed only using api_key +belonging to user with admin rights. + +INPUT:: + + api_key : "" + method : "create_user" + args : { + "username" : "", + "password" : "", + "firstname" : "", + "lastname" : "", + "email" : "" + "active" : " = True", + "admin" : " = False", + "ldap_dn" : " = None" + } OUTPUT:: - result:{"id": , - "msg":"created new user "} - error:null - - + result: { + "msg" : "created new user " + } + error: null + +get_users_groups +---------------- + +Lists all existing users groups. This command can be executed only using api_key +belonging to user with admin rights. + +INPUT:: + + api_key : "" + method : "get_users_groups" + args : { } + +OUTPUT:: + + result : [ + { + "id" : "", + "name" : "", + "active": "", + "members" : [ + { + "id" : "", + "username" : "", + "firstname": "", + "lastname" : "", + "email" : "", + "active" : "", + "admin" :  "", + "ldap" : "" + }, + … + ] + } + ] + error : null + +get_users_group +--------------- + +Gets an existing users group. This command can be executed only using api_key +belonging to user with admin rights. + +INPUT:: + + api_key : "" + method : "get_users_group" + args : { + "group_name" : "" + } + +OUTPUT:: + + result : None if group not exist + { + "id" : "", + "name" : "", + "active": "", + "members" : [ + { "id" : "", + "username" : "", + "firstname": "", + "lastname" : "", + "email" : "", + "active" : "", + "admin" :  "", + "ldap" : "" + }, + … + ] + } + error : null + create_users_group ------------------ -creates new users group. This command can be executed only using api_key +Creates new users group. This command can be executed only using api_key belonging to user with admin rights INPUT:: - api_key:"" - method: "create_user" - args: {"name": "", - "active":""} + api_key : "" + method : "create_users_group" + args: { + "name": "", + "active":" = True" + } + +OUTPUT:: + + result: { + "id": "", + "msg": "created new users group " + } + error: null + +add_user_to_users_groups +------------------------ + +Adds a user to a users group. This command can be executed only using api_key +belonging to user with admin rights + +INPUT:: + + api_key : "" + method : "add_user_users_group" + args: { + "group_name" : "", + "user_name" : "" + } + +OUTPUT:: + + result: { + "id": "", + "msg": "created new users group member" + } + error: null + +get_repos +--------- + +Lists all existing repositories. This command can be executed only using api_key +belonging to user with admin rights + +INPUT:: + + api_key : "" + method : "get_repos" + args: { } OUTPUT:: - result:{"id": , - "msg":"created new users group "} - error:null + result: [ + { + "id" : "", + "name" : "" + "type" : "", + "description" : "" + }, + … + ] + error: null + +get_repo +-------- + +Gets an existing repository. This command can be executed only using api_key +belonging to user with admin rights + +INPUT:: + + api_key : "" + method : "get_repo" + args: { + "name" : "" + } + +OUTPUT:: + + result: None if repository not exist + { + "id" : "", + "name" : "" + "type" : "", + "description" : "", + "members" : [ + { "id" : "", + "username" : "", + "firstname": "", + "lastname" : "", + "email" : "", + "active" : "", + "admin" :  "", + "ldap" : "", + "permission" : "repository_(read|write|admin)" + }, + … + { + "id" : "", + "name" : "", + "active": "", + "permission" : "repository_(read|write|admin)" + }, + … + ] + } + error: null + +create_repo +----------- + +Creates a repository. This command can be executed only using api_key +belonging to user with admin rights. +If repository name contains "/", all needed repository groups will be created. +For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent), +and create "baz" repository with "bar" as group. + +INPUT:: + + api_key : "" + method : "create_repo" + args: { + "name" : "", + "owner_name" : "", + "description" : " = ''", + "repo_type" : " = 'hg'", + "private" : " = False" + } + +OUTPUT:: + + result: None + error: null + +add_user_to_repo +---------------- + +Add a user to a repository. This command can be executed only using api_key +belonging to user with admin rights. +If "perm" is None, user will be removed from the repository. + +INPUT:: + + api_key : "" + method : "add_user_to_repo" + args: { + "repo_name" : "", + "user_name" : "", + "perm" : "(None|repository_(read|write|admin))", + } + +OUTPUT:: + + result: None + error: null diff --git a/production.ini b/production.ini --- a/production.ini +++ b/production.ini @@ -24,6 +24,8 @@ pdebug = false #smtp_port = #smtp_use_tls = false #smtp_use_ssl = true +# Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.) +#smtp_auth = [server:main] ##nr of threads to spawn diff --git a/rhodecode/config/deployment.ini_tmpl b/rhodecode/config/deployment.ini_tmpl --- a/rhodecode/config/deployment.ini_tmpl +++ b/rhodecode/config/deployment.ini_tmpl @@ -24,6 +24,8 @@ pdebug = false #smtp_port = #smtp_use_tls = false #smtp_use_ssl = true +# Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.) +#smtp_auth = [server:main] ##nr of threads to spawn @@ -146,7 +148,7 @@ logview.pylons.util = #eee # SQLITE [default] sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db -# POSTGRES +# POSTGRESQL # sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode # MySQL diff --git a/rhodecode/controllers/admin/repos.py b/rhodecode/controllers/admin/repos.py --- a/rhodecode/controllers/admin/repos.py +++ b/rhodecode/controllers/admin/repos.py @@ -26,7 +26,6 @@ import logging import traceback import formencode -from operator import itemgetter from formencode import htmlfill from paste.httpexceptions import HTTPInternalServerError @@ -92,7 +91,7 @@ class ReposController(BaseController): return redirect(url('repos')) c.default_user_id = User.get_by_username('default').user_id - c.in_public_journal = self.sa.query(UserFollowing)\ + c.in_public_journal = UserFollowing.query()\ .filter(UserFollowing.user_id == c.default_user_id)\ .filter(UserFollowing.follows_repository == c.repo_info).scalar() @@ -110,30 +109,7 @@ class ReposController(BaseController): c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100) - defaults = c.repo_info.get_dict() - group, repo_name = c.repo_info.groups_and_repo - defaults['repo_name'] = repo_name - defaults['repo_group'] = getattr(group[-1] if group else None, - 'group_id', None) - - #fill owner - if c.repo_info.user: - defaults.update({'user': c.repo_info.user.username}) - else: - replacement_user = self.sa.query(User)\ - .filter(User.admin == True).first().username - defaults.update({'user': replacement_user}) - - #fill repository users - for p in c.repo_info.repo_to_perm: - defaults.update({'u_perm_%s' % p.user.username: - p.permission.permission_name}) - - #fill repository groups - for p in c.repo_info.users_group_to_perm: - defaults.update({'g_perm_%s' % p.users_group.users_group_name: - p.permission.permission_name}) - + defaults = RepoModel()._get_defaults(repo_name) return defaults @HasPermissionAllDecorator('hg.admin') diff --git a/rhodecode/controllers/api/api.py b/rhodecode/controllers/api/api.py --- a/rhodecode/controllers/api/api.py +++ b/rhodecode/controllers/api/api.py @@ -2,10 +2,18 @@ import traceback import logging from rhodecode.controllers.api import JSONRPCController, JSONRPCError -from rhodecode.lib.auth import HasPermissionAllDecorator +from rhodecode.lib.auth import HasPermissionAllDecorator, \ + HasPermissionAnyDecorator from rhodecode.model.scm import ScmModel -from rhodecode.model.db import User, UsersGroup, Repository +from rhodecode.model.db import User, UsersGroup, Group, Repository +from rhodecode.model.repo import RepoModel +from rhodecode.model.user import UserModel +from rhodecode.model.repo_permission import RepositoryPermissionModel +from rhodecode.model.users_group import UsersGroupModel +from rhodecode.model import users_group +from rhodecode.model.repos_group import ReposGroupModel +from sqlalchemy.orm.exc import NoResultFound log = logging.getLogger(__name__) @@ -13,86 +21,354 @@ log = logging.getLogger(__name__) class ApiController(JSONRPCController): """ API Controller - - + + Each method needs to have USER as argument this is then based on given API_KEY propagated as instance of user object - + Preferably this should be first argument also - - - Each function should also **raise** JSONRPCError for any + + + Each function should also **raise** JSONRPCError for any errors that happens - + """ @HasPermissionAllDecorator('hg.admin') def pull(self, apiuser, repo): """ Dispatch pull action on given repo - - + + :param user: :param repo: """ if Repository.is_valid(repo) is False: raise JSONRPCError('Unknown repo "%s"' % repo) - + try: ScmModel().pull_changes(repo, self.rhodecode_user.username) return 'Pulled from %s' % repo except Exception: raise JSONRPCError('Unable to pull changes from "%s"' % repo) + @HasPermissionAllDecorator('hg.admin') + def get_user(self, apiuser, username): + """" + Get a user by username + + :param apiuser + :param username + """ + + user = User.get_by_username(username) + if not user: + return None + + return dict(id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + active=user.active, + admin=user.admin, + ldap=user.ldap_dn) @HasPermissionAllDecorator('hg.admin') - def create_user(self, apiuser, username, password, active, admin, name, - lastname, email): + def get_users(self, apiuser): + """" + Get all users + + :param apiuser """ - Creates new user - + + result = [] + for user in User.getAll(): + result.append(dict(id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + active=user.active, + admin=user.admin, + ldap=user.ldap_dn)) + return result + + @HasPermissionAllDecorator('hg.admin') + def create_user(self, apiuser, username, password, firstname, + lastname, email, active=True, admin=False, ldap_dn=None): + """ + Create new user + :param apiuser: :param username: :param password: - :param active: - :param admin: :param name: :param lastname: :param email: + :param active: + :param admin: + :param ldap_dn: """ - - form_data = dict(username=username, - password=password, - active=active, - admin=admin, - name=name, - lastname=lastname, - email=email) + + if self.get_user(apiuser, username): + raise JSONRPCError("user %s already exist" % username) + try: - u = User.create(form_data) - return {'id':u.user_id, - 'msg':'created new user %s' % name} + form_data = dict(username=username, + password=password, + active=active, + admin=admin, + name=firstname, + lastname=lastname, + email=email, + ldap_dn=ldap_dn) + UserModel().create_ldap(username, password, ldap_dn, form_data) + return dict(msg='created new user %s' % username) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to create user %s' % name) + raise JSONRPCError('failed to create user %s' % username) + + @HasPermissionAllDecorator('hg.admin') + def get_users_group(self, apiuser, group_name): + """" + Get users group by name + + :param apiuser + :param group_name + """ + + users_group = UsersGroup.get_by_group_name(group_name) + if not users_group: + return None + members = [] + for user in users_group.members: + user = user.user + members.append(dict(id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + active=user.active, + admin=user.admin, + ldap=user.ldap_dn)) + + return dict(id=users_group.users_group_id, + name=users_group.users_group_name, + active=users_group.users_group_active, + members=members) @HasPermissionAllDecorator('hg.admin') - def create_users_group(self, apiuser, name, active): + def get_users_groups(self, apiuser): + """" + Get all users groups + + :param apiuser + """ + + result = [] + for users_group in UsersGroup.getAll(): + members = [] + for user in users_group.members: + user = user.user + members.append(dict(id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + active=user.active, + admin=user.admin, + ldap=user.ldap_dn)) + + result.append(dict(id=users_group.users_group_id, + name=users_group.users_group_name, + active=users_group.users_group_active, + members=members)) + return result + + @HasPermissionAllDecorator('hg.admin') + def create_users_group(self, apiuser, name, active=True): """ Creates an new usergroup - + :param name: :param active: """ - form_data = {'users_group_name':name, - 'users_group_active':active} + + if self.get_users_group(apiuser, name): + raise JSONRPCError("users group %s already exist" % name) + try: + form_data = dict(users_group_name=name, + users_group_active=active) ug = UsersGroup.create(form_data) - return {'id':ug.users_group_id, - 'msg':'created new users group %s' % name} + return dict(id=ug.users_group_id, + msg='created new users group %s' % name) except Exception: log.error(traceback.format_exc()) raise JSONRPCError('failed to create group %s' % name) - \ No newline at end of file + + @HasPermissionAllDecorator('hg.admin') + def add_user_to_users_group(self, apiuser, group_name, user_name): + """" + Add a user to a group + + :param apiuser + :param group_name + :param user_name + """ + + try: + users_group = UsersGroup.get_by_group_name(group_name) + if not users_group: + raise JSONRPCError('unknown users group %s' % group_name) + + try: + user = User.get_by_username(user_name) + except NoResultFound: + raise JSONRPCError('unknown user %s' % user_name) + + ugm = UsersGroupModel().add_user_to_group(users_group, user) + + return dict(id=ugm.users_group_member_id, + msg='created new users group member') + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to create users group member') + + @HasPermissionAnyDecorator('hg.admin') + def get_repo(self, apiuser, repo_name): + """" + Get repository by name + + :param apiuser + :param repo_name + """ + + try: + repo = Repository.get_by_repo_name(repo_name) + except NoResultFound: + return None + + members = [] + for user in repo.repo_to_perm: + perm = user.permission.permission_name + user = user.user + members.append(dict(type_="user", + id=user.user_id, + username=user.username, + firstname=user.name, + lastname=user.lastname, + email=user.email, + active=user.active, + admin=user.admin, + ldap=user.ldap_dn, + permission=perm)) + for users_group in repo.users_group_to_perm: + perm = users_group.permission.permission_name + users_group = users_group.users_group + members.append(dict(type_="users_group", + id=users_group.users_group_id, + name=users_group.users_group_name, + active=users_group.users_group_active, + permission=perm)) + + return dict(id=repo.repo_id, + name=repo.repo_name, + type=repo.repo_type, + description=repo.description, + members=members) + + @HasPermissionAnyDecorator('hg.admin') + def get_repos(self, apiuser): + """" + Get all repositories + + :param apiuser + """ + + result = [] + for repository in Repository.getAll(): + result.append(dict(id=repository.repo_id, + name=repository.repo_name, + type=repository.repo_type, + description=repository.description)) + return result + + @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') + def create_repo(self, apiuser, name, owner_name, description='', + repo_type='hg', private=False): + """ + Create a repository + + :param apiuser + :param name + :param description + :param type + :param private + :param owner_name + """ + + try: + try: + owner = User.get_by_username(owner_name) + except NoResultFound: + raise JSONRPCError('unknown user %s' % owner) + + if self.get_repo(apiuser, name): + raise JSONRPCError("repo %s already exist" % name) + + groups = name.split('/') + real_name = groups[-1] + groups = groups[:-1] + parent_id = None + for g in groups: + group = Group.get_by_group_name(g) + if not group: + group = ReposGroupModel().create(dict(group_name=g, + group_description='', + group_parent_id=parent_id)) + parent_id = group.group_id + + RepoModel().create(dict(repo_name=real_name, + repo_name_full=name, + description=description, + private=private, + repo_type=repo_type, + repo_group=parent_id, + clone_uri=None), owner) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to create repository %s' % name) + + @HasPermissionAnyDecorator('hg.admin') + def add_user_to_repo(self, apiuser, repo_name, user_name, perm): + """ + Add permission for a user to a repository + + :param apiuser + :param repo_name + :param user_name + :param perm + """ + + try: + try: + repo = Repository.get_by_repo_name(repo_name) + except NoResultFound: + raise JSONRPCError('unknown repository %s' % repo) + + try: + user = User.get_by_username(user_name) + except NoResultFound: + raise JSONRPCError('unknown user %s' % user) + + RepositoryPermissionModel()\ + .update_or_delete_user_permission(repo, user, perm) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to edit permission %(repo)s for %(user)s' + % dict(user=user_name, repo=repo_name)) + diff --git a/rhodecode/controllers/settings.py b/rhodecode/controllers/settings.py --- a/rhodecode/controllers/settings.py +++ b/rhodecode/controllers/settings.py @@ -42,7 +42,7 @@ from rhodecode.lib.utils import invalida from rhodecode.model.forms import RepoSettingsForm, RepoForkForm from rhodecode.model.repo import RepoModel -from rhodecode.model.db import User +from rhodecode.model.db import Group log = logging.getLogger(__name__) @@ -52,7 +52,15 @@ class SettingsController(BaseRepoControl @LoginRequired() def __before__(self): super(SettingsController, self).__before__() - + + def __load_defaults(self): + c.repo_groups = Group.groups_choices() + c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) + + repo_model = RepoModel() + c.users_array = repo_model.get_users_js() + c.users_groups_array = repo_model.get_users_groups_js() + @HasRepoPermissionAllDecorator('repository.admin') def index(self, repo_name): repo_model = RepoModel() @@ -66,28 +74,9 @@ class SettingsController(BaseRepoControl return redirect(url('home')) - c.users_array = repo_model.get_users_js() - c.users_groups_array = repo_model.get_users_groups_js() - - defaults = c.repo_info.get_dict() + self.__load_defaults() - #fill owner - if c.repo_info.user: - defaults.update({'user': c.repo_info.user.username}) - else: - replacement_user = self.sa.query(User)\ - .filter(User.admin == True).first().username - defaults.update({'user': replacement_user}) - - #fill repository users - for p in c.repo_info.repo_to_perm: - defaults.update({'u_perm_%s' % p.user.username: - p.permission.permission_name}) - - #fill repository groups - for p in c.repo_info.users_group_to_perm: - defaults.update({'g_perm_%s' % p.users_group.users_group_name: - p.permission.permission_name}) + defaults = RepoModel()._get_defaults(repo_name) return htmlfill.render( render('settings/repo_settings.html'), @@ -100,17 +89,22 @@ class SettingsController(BaseRepoControl def update(self, repo_name): repo_model = RepoModel() changed_name = repo_name + + self.__load_defaults() + _form = RepoSettingsForm(edit=True, - old_data={'repo_name': repo_name})() + old_data={'repo_name': repo_name}, + repo_groups=c.repo_groups_choices)() try: form_result = _form.to_python(dict(request.POST)) + repo_model.update(repo_name, form_result) invalidate_cache('get_repo_cached_%s' % repo_name) h.flash(_('Repository %s updated successfully' % repo_name), category='success') - changed_name = form_result['repo_name'] + changed_name = form_result['repo_name_full'] action_logger(self.rhodecode_user, 'user_updated_repo', - changed_name, '', self.sa) + changed_name, '', self.sa) except formencode.Invalid, errors: c.repo_info = repo_model.get_by_repo_name(repo_name) c.users_array = repo_model.get_users_js() diff --git a/rhodecode/lib/__init__.py b/rhodecode/lib/__init__.py --- a/rhodecode/lib/__init__.py +++ b/rhodecode/lib/__init__.py @@ -394,13 +394,12 @@ def get_current_revision(quiet=False): try: from vcs import get_repo from vcs.utils.helpers import get_scm - from vcs.exceptions import RepositoryError, VCSError repopath = os.path.join(os.path.dirname(__file__), '..', '..') scm = get_scm(repopath)[0] repo = get_repo(path=repopath, alias=scm) tip = repo.get_changeset() return (tip.revision, tip.short_id) - except (ImportError, RepositoryError, VCSError), err: + except Exception, err: if not quiet: print ("Cannot retrieve rhodecode's revision. Original error " "was: %s" % err) diff --git a/rhodecode/lib/auth_ldap.py b/rhodecode/lib/auth_ldap.py --- a/rhodecode/lib/auth_ldap.py +++ b/rhodecode/lib/auth_ldap.py @@ -53,8 +53,10 @@ class AuthLdap(object): if self.TLS_KIND == 'LDAPS': port = port or 689 ldap_server_type = ldap_server_type + 's' - - self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert] + + OPT_X_TLS_DEMAND = 2 + self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, + OPT_X_TLS_DEMAND) self.LDAP_SERVER_ADDRESS = server self.LDAP_SERVER_PORT = port @@ -63,12 +65,12 @@ class AuthLdap(object): self.LDAP_BIND_PASS = bind_pass self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type, - self.LDAP_SERVER_ADDRESS, - self.LDAP_SERVER_PORT) + self.LDAP_SERVER_ADDRESS, + self.LDAP_SERVER_PORT) self.BASE_DN = base_dn self.LDAP_FILTER = ldap_filter - self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope] + self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope) self.attr_login = attr_login def authenticate_ldap(self, username, password): @@ -88,7 +90,9 @@ class AuthLdap(object): if "," in username: raise LdapUsernameError("invalid character in username: ,") try: - ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts') + if hasattr(ldap,'OPT_X_TLS_CACERTDIR'): + ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, + '/etc/openldap/cacerts') ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) ldap.set_option(ldap.OPT_TIMEOUT, 20) diff --git a/rhodecode/lib/celerylib/tasks.py b/rhodecode/lib/celerylib/tasks.py --- a/rhodecode/lib/celerylib/tasks.py +++ b/rhodecode/lib/celerylib/tasks.py @@ -356,9 +356,10 @@ def send_email(recipients, subject, body tls = str2bool(email_config.get('smtp_use_tls')) ssl = str2bool(email_config.get('smtp_use_ssl')) debug = str2bool(config.get('debug')) + smtp_auth = email_config.get('smtp_auth') try: - m = SmtpMailer(mail_from, user, passwd, mail_server, + m = SmtpMailer(mail_from, user, passwd, mail_server,smtp_auth, mail_port, ssl, tls, debug=debug) m.send(recipients, subject, body) except: diff --git a/rhodecode/lib/middleware/simplegit.py b/rhodecode/lib/middleware/simplegit.py --- a/rhodecode/lib/middleware/simplegit.py +++ b/rhodecode/lib/middleware/simplegit.py @@ -167,6 +167,8 @@ class SimpleGit(object): username = REMOTE_USER(environ) try: user = self.__get_user(username) + if user is None: + return HTTPForbidden()(environ, start_response) username = user.username except: log.error(traceback.format_exc()) diff --git a/rhodecode/lib/middleware/simplehg.py b/rhodecode/lib/middleware/simplehg.py --- a/rhodecode/lib/middleware/simplehg.py +++ b/rhodecode/lib/middleware/simplehg.py @@ -133,6 +133,8 @@ class SimpleHg(object): username = REMOTE_USER(environ) try: user = self.__get_user(username) + if user is None: + return HTTPForbidden()(environ, start_response) username = user.username except: log.error(traceback.format_exc()) diff --git a/rhodecode/lib/smtp_mailer.py b/rhodecode/lib/smtp_mailer.py --- a/rhodecode/lib/smtp_mailer.py +++ b/rhodecode/lib/smtp_mailer.py @@ -39,7 +39,7 @@ from email import encoders class SmtpMailer(object): """SMTP mailer class - mailer = SmtpMailer(mail_from, user, passwd, mail_server, + mailer = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth mail_port, ssl, tls) mailer.send(recipients, subject, body, attachment_files) @@ -49,8 +49,8 @@ class SmtpMailer(object): """ - def __init__(self, mail_from, user, passwd, mail_server, - mail_port=None, ssl=False, tls=False, debug=False): + def __init__(self, mail_from, user, passwd, mail_server, smtp_auth=None, + mail_port=None, ssl=False, tls=False, debug=False): self.mail_from = mail_from self.mail_server = mail_server @@ -60,6 +60,7 @@ class SmtpMailer(object): self.ssl = ssl self.tls = tls self.debug = debug + self.auth = smtp_auth def send(self, recipients=[], subject='', body='', attachment_files=None): @@ -78,9 +79,11 @@ class SmtpMailer(object): smtp_serv.set_debuglevel(1) smtp_serv.ehlo() + if self.auth: + smtp_serv.esmtp_features["auth"] = self.auth - #if server requires authorization you must provide login and password - #but only if we have them + # if server requires authorization you must provide login and password + # but only if we have them if self.user and self.passwd: smtp_serv.login(self.user, self.passwd) @@ -156,6 +159,7 @@ class SmtpMailer(object): if isinstance(msg_file, str): return open(msg_file, "rb").read() else: - #just for safe seek to 0 + # just for safe seek to 0 msg_file.seek(0) return msg_file.read() + diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -30,11 +30,8 @@ import traceback from datetime import date from sqlalchemy import * -from sqlalchemy.exc import DatabaseError from sqlalchemy.ext.hybrid import hybrid_property -from sqlalchemy.orm import relationship, backref, joinedload, class_mapper, \ - validates -from sqlalchemy.orm.interfaces import MapperExtension +from sqlalchemy.orm import relationship, joinedload, class_mapper, validates from beaker.cache import cache_region, region_invalidate from vcs import get_backend @@ -60,24 +57,24 @@ log = logging.getLogger(__name__) class ModelSerializer(json.JSONEncoder): """ Simple Serializer for JSON, - + usage:: - + to make object customized for serialization implement a __json__ method that will return a dict for serialization into json - + example:: - + class Task(object): - + def __init__(self, name, value): self.name = name self.value = value - + def __json__(self): return dict(name=self.name, - value=self.value) - + value=self.value) + """ def default(self, obj): @@ -129,11 +126,15 @@ class BaseModel(object): @classmethod def get(cls, id_): if id_: - return Session.query(cls).get(id_) + return cls.query().get(id_) + + @classmethod + def getAll(cls): + return cls.query().all() @classmethod def delete(cls, id_): - obj = Session.query(cls).get(id_) + obj = cls.query().get(id_) Session.delete(obj) Session.commit() @@ -160,13 +161,13 @@ class RhodeCodeSettings(Base, BaseModel) v = self._app_settings_value if v == 'ldap_active': v = str2bool(v) - return v + return v @app_settings_value.setter - def app_settings_value(self,val): + def app_settings_value(self, val): """ Setter that will always make sure we use unicode in app_settings_value - + :param val: """ self._app_settings_value = safe_unicode(val) @@ -178,13 +179,13 @@ class RhodeCodeSettings(Base, BaseModel) @classmethod def get_by_name(cls, ldap_key): - return Session.query(cls)\ + return cls.query()\ .filter(cls.app_settings_name == ldap_key).scalar() @classmethod def get_app_settings(cls, cache=False): - ret = Session.query(cls) + ret = cls.query() if cache: ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) @@ -200,7 +201,7 @@ class RhodeCodeSettings(Base, BaseModel) @classmethod def get_ldap_settings(cls, cache=False): - ret = Session.query(cls)\ + ret = cls.query()\ .filter(cls.app_settings_name.startswith('ldap_')).all() fd = {} for row in ret: @@ -227,7 +228,7 @@ class RhodeCodeUi(Base, BaseModel): @classmethod def get_by_key(cls, key): - return Session.query(cls).filter(cls.ui_key == key) + return cls.query().filter(cls.ui_key == key) @classmethod @@ -311,7 +312,7 @@ class User(Base, BaseModel): @classmethod def get_by_api_key(cls, api_key): - return Session.query(cls).filter(cls.api_key == api_key).one() + return cls.query().filter(cls.api_key == api_key).one() def update_lastlogin(self): """Update user lastlogin""" @@ -376,11 +377,11 @@ class UsersGroup(Base, BaseModel): @classmethod def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): if case_insensitive: - gr = Session.query(cls)\ - .filter(cls.users_group_name.ilike(group_name)) + gr = cls.query()\ + .filter(cls.users_group_name.ilike(group_name)) else: - gr = Session.query(UsersGroup)\ - .filter(UsersGroup.users_group_name == group_name) + gr = cls.query()\ + .filter(cls.users_group_name == group_name) if cache: gr = gr.options(FromCache("sql_cache_short", "get_user_%s" % group_name)) @@ -389,7 +390,7 @@ class UsersGroup(Base, BaseModel): @classmethod def get(cls, users_group_id, cache=False): - users_group = Session.query(cls) + users_group = cls.query() if cache: users_group = users_group.options(FromCache("sql_cache_short", "get_users_group_%s" % users_group_id)) @@ -422,10 +423,10 @@ class UsersGroup(Base, BaseModel): Session.flush() members_list = [] if v: + v = [v] if isinstance(v, basestring) else v for u_id in set(v): - members_list.append(UsersGroupMember( - users_group_id, - u_id)) + member = UsersGroupMember(users_group_id, u_id) + members_list.append(member) setattr(users_group, 'members', members_list) setattr(users_group, k, v) @@ -457,7 +458,6 @@ class UsersGroup(Base, BaseModel): Session.rollback() raise - class UsersGroupMember(Base, BaseModel): __tablename__ = 'users_groups_members' __table_args__ = {'extend_existing':True} @@ -473,6 +473,15 @@ class UsersGroupMember(Base, BaseModel): self.users_group_id = gr_id self.user_id = u_id + @staticmethod + def add_user_to_group(group, user): + ugm = UsersGroupMember() + ugm.users_group = group + ugm.user = user + Session.add(ugm) + Session.commit() + return ugm + class Repository(Base, BaseModel): __tablename__ = 'repositories' __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},) @@ -510,29 +519,27 @@ class Repository(Base, BaseModel): @classmethod def url_sep(cls): return '/' - + @classmethod def get_by_repo_name(cls, repo_name): q = Session.query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ .options(joinedload(Repository.user))\ - .options(joinedload(Repository.group))\ - + .options(joinedload(Repository.group)) return q.one() @classmethod def get_repo_forks(cls, repo_id): - return Session.query(cls).filter(Repository.fork_id == repo_id) + return cls.query().filter(Repository.fork_id == repo_id) @classmethod def base_path(cls): """ Returns base path when all repos are stored - + :param cls: """ - q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == cls.url_sep()) q.options(FromCache("sql_cache_short", "repository_repo_path")) return q.one().ui_value @@ -568,7 +575,7 @@ class Repository(Base, BaseModel): Returns base full path for that repository means where it actually exists on a filesystem """ - q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == Repository.url_sep()) q.options(FromCache("sql_cache_short", "repository_repo_path")) return q.one().ui_value @@ -585,7 +592,7 @@ class Repository(Base, BaseModel): def get_new_name(self, repo_name): """ returns new full repository name based on assigned group and new new - + :param group_name: """ path_prefix = self.group.full_path_splitted if self.group else [] @@ -606,7 +613,7 @@ class Repository(Base, BaseModel): baseui._tcfg = config.config() - ret = Session.query(RhodeCodeUi)\ + ret = RhodeCodeUi.query()\ .options(FromCache("sql_cache_short", "repository_repo_ui")).all() hg_ui = ret @@ -622,7 +629,7 @@ class Repository(Base, BaseModel): def is_valid(cls, repo_name): """ returns True if given repo name is a valid filesystem repository - + @param cls: @param repo_name: """ @@ -661,7 +668,7 @@ class Repository(Base, BaseModel): None otherwise. `cache_active = False` means that this cache state is not valid and needs to be invalidated """ - return Session.query(CacheInvalidation)\ + return CacheInvalidation.query()\ .filter(CacheInvalidation.cache_key == self.repo_name)\ .filter(CacheInvalidation.cache_active == False)\ .scalar() @@ -670,7 +677,7 @@ class Repository(Base, BaseModel): """ set a cache for invalidation for this instance """ - inv = Session.query(CacheInvalidation)\ + inv = CacheInvalidation.query()\ .filter(CacheInvalidation.cache_key == self.repo_name)\ .scalar() @@ -763,17 +770,26 @@ class Group(Base, BaseModel): repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) for x in cls.query().all()]) - - repo_groups = sorted(repo_groups,key=lambda t: t[1].split(sep)[0]) + + repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) return repo_groups - + @classmethod def url_sep(cls): return '/' @classmethod - def get_by_group_name(cls, group_name): - return cls.query().filter(cls.group_name == group_name).scalar() + def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): + if case_insensitive: + gr = cls.query()\ + .filter(cls.group_name.ilike(group_name)) + else: + gr = cls.query()\ + .filter(cls.group_name == group_name) + if cache: + gr = gr.options(FromCache("sql_cache_short", + "get_group_%s" % group_name)) + return gr.scalar() @property def parents(self): @@ -801,7 +817,7 @@ class Group(Base, BaseModel): @property def children(self): - return Session.query(Group).filter(Group.parent_group == self) + return Group.query().filter(Group.parent_group == self) @property def name(self): @@ -817,7 +833,7 @@ class Group(Base, BaseModel): @property def repositories(self): - return Session.query(Repository).filter(Repository.group == self) + return Repository.query().filter(Repository.group == self) @property def repositories_recursive_count(self): @@ -836,10 +852,11 @@ class Group(Base, BaseModel): def get_new_name(self, group_name): """ returns new full group name based on parent and new name - + :param group_name: """ - path_prefix = self.parent_group.full_path_splitted if self.parent_group else [] + path_prefix = (self.parent_group.full_path_splitted if + self.parent_group else []) return Group.url_sep().join(path_prefix + [group_name]) @@ -856,7 +873,7 @@ class Permission(Base, BaseModel): @classmethod def get_by_key(cls, key): - return Session.query(cls).filter(cls.permission_name == key).scalar() + return cls.query().filter(cls.permission_name == key).scalar() class RepoToPerm(Base, BaseModel): __tablename__ = 'repo_to_perm' @@ -885,7 +902,7 @@ class UserToPerm(Base, BaseModel): if not isinstance(perm, Permission): raise Exception('perm needs to be an instance of Permission class') - return Session.query(cls).filter(cls.user_id == user_id)\ + return cls.query().filter(cls.user_id == user_id)\ .filter(cls.permission == perm).scalar() is not None @classmethod @@ -909,7 +926,7 @@ class UserToPerm(Base, BaseModel): raise Exception('perm needs to be an instance of Permission class') try: - Session.query(cls).filter(cls.user_id == user_id)\ + cls.query().filter(cls.user_id == user_id)\ .filter(cls.permission == perm).delete() Session.commit() except: @@ -945,7 +962,7 @@ class UsersGroupToPerm(Base, BaseModel): if not isinstance(perm, Permission): raise Exception('perm needs to be an instance of Permission class') - return Session.query(cls).filter(cls.users_group_id == + return cls.query().filter(cls.users_group_id == users_group_id)\ .filter(cls.permission == perm)\ .scalar() is not None @@ -971,7 +988,7 @@ class UsersGroupToPerm(Base, BaseModel): raise Exception('perm needs to be an instance of Permission class') try: - Session.query(cls).filter(cls.users_group_id == users_group_id)\ + cls.query().filter(cls.users_group_id == users_group_id)\ .filter(cls.permission == perm).delete() Session.commit() except: @@ -1023,7 +1040,7 @@ class UserFollowing(Base, BaseModel): @classmethod def get_repo_followers(cls, repo_id): - return Session.query(cls).filter(cls.follows_repo_id == repo_id) + return cls.query().filter(cls.follows_repo_id == repo_id) class CacheInvalidation(Base, BaseModel): __tablename__ = 'cache_invalidation' @@ -1049,3 +1066,4 @@ class DbMigrateVersion(Base, BaseModel): repository_id = Column('repository_id', String(250), primary_key=True) repository_path = Column('repository_path', Text) version = Column('version', Integer) + diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -185,20 +185,21 @@ class ValidPassword(formencode.validator class ValidPasswordsMatch(formencode.validators.FancyValidator): def validate_python(self, value, state): - - if value['password'] != value['password_confirmation']: + + pass_val = value.get('password') or value.get('new_password') + if pass_val != value['password_confirmation']: e_dict = {'password_confirmation': _('Passwords do not match')} raise formencode.Invalid('', value, state, error_dict=e_dict) class ValidAuth(formencode.validators.FancyValidator): messages = { - 'invalid_password':_('invalid password'), - 'invalid_login':_('invalid user name'), - 'disabled_account':_('Your account is disabled') - - } - #error mapping + 'invalid_password':_('invalid password'), + 'invalid_login':_('invalid user name'), + 'disabled_account':_('Your account is disabled') + } + + # error mapping e_dict = {'username':messages['invalid_login'], 'password':messages['invalid_password']} e_dict_disable = {'username':messages['disabled_account']} @@ -253,6 +254,7 @@ def ValidRepoName(edit, old_data): # db key This is an actual just the name to store in the # database repo_name_full = group_path + Group.url_sep() + repo_name + else: group_path = '' repo_name_full = repo_name @@ -496,8 +498,6 @@ class LoginForm(formencode.Schema): 'tooShort':_('Enter %(min)i characters or more')} ) - - #chained validators have access to all data chained_validators = [ValidAuth] def UserForm(edit=False, old_data={}): @@ -508,15 +508,18 @@ def UserForm(edit=False, old_data={}): ValidUsername(edit, old_data)) if edit: new_password = All(UnicodeString(strip=True, min=6, not_empty=False)) + password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False)) admin = StringBoolean(if_missing=False) else: password = All(UnicodeString(strip=True, min=6, not_empty=True)) + password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False)) + active = StringBoolean(if_missing=False) name = UnicodeString(strip=True, min=1, not_empty=True) lastname = UnicodeString(strip=True, min=1, not_empty=True) email = All(Email(not_empty=True), UniqSystemEmail(old_data)) - chained_validators = [ValidPassword] + chained_validators = [ValidPasswordsMatch, ValidPassword] return _UserForm @@ -616,16 +619,19 @@ def RepoForkForm(edit=False, old_data={} return _RepoForkForm -def RepoSettingsForm(edit=False, old_data={}): +def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), + repo_groups=[]): class _RepoForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), SlugifyName()) description = UnicodeString(strip=True, min=1, not_empty=True) + repo_group = OneOf(repo_groups, hideList=True) private = StringBoolean(if_missing=False) - chained_validators = [ValidRepoName(edit, old_data), ValidPerms, ValidSettings] + chained_validators = [ValidRepoName(edit, old_data), ValidPerms, + ValidSettings] return _RepoForm diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -94,6 +94,46 @@ class RepoModel(BaseModel): for gr in users_groups]) return users_groups_array + def _get_defaults(self, repo_name): + """ + Get's information about repository, and returns a dict for + usage in forms + + :param repo_name: + """ + + repo_info = Repository.get_by_repo_name(repo_name) + + if repo_info is None: + return None + + defaults = repo_info.get_dict() + group, repo_name = repo_info.groups_and_repo + defaults['repo_name'] = repo_name + defaults['repo_group'] = getattr(group[-1] if group else None, + 'group_id', None) + + # fill owner + if repo_info.user: + defaults.update({'user': repo_info.user.username}) + else: + replacement_user = User.query().filter(User.admin == + True).first().username + defaults.update({'user': replacement_user}) + + # fill repository users + for p in repo_info.repo_to_perm: + defaults.update({'u_perm_%s' % p.user.username: + p.permission.permission_name}) + + # fill repository groups + for p in repo_info.users_group_to_perm: + defaults.update({'g_perm_%s' % p.users_group.users_group_name: + p.permission.permission_name}) + + return defaults + + def update(self, repo_name, form_data): try: cur_repo = self.get_by_repo_name(repo_name, cache=False) @@ -151,7 +191,7 @@ class RepoModel(BaseModel): elif k == 'repo_name': pass elif k == 'repo_group': - cur_repo.group_id = v + cur_repo.group = Group.get(v) else: setattr(cur_repo, k, v) @@ -305,7 +345,7 @@ class RepoModel(BaseModel): :param parent_id: :param clone_uri: """ - from rhodecode.lib.utils import is_valid_repo,is_valid_repos_group + from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group if new_parent_id: paths = Group.get(new_parent_id).full_path.split(Group.url_sep()) @@ -316,7 +356,7 @@ class RepoModel(BaseModel): repo_path = os.path.join(*map(lambda x:safe_str(x), [self.repos_path, new_parent_path, repo_name])) - + # check if this path is not a repository if is_valid_repo(repo_path, self.repos_path): raise Exception('This path %s is a valid repository' % repo_path) @@ -324,7 +364,7 @@ class RepoModel(BaseModel): # check if this path is a group if is_valid_repos_group(repo_path, self.repos_path): raise Exception('This path %s is a valid group' % repo_path) - + log.info('creating repo %s in %s @ %s', repo_name, repo_path, clone_uri) backend = get_backend(alias) @@ -368,3 +408,4 @@ class RepoModel(BaseModel): % (datetime.today()\ .strftime('%Y%m%d_%H%M%S_%f'), repo.repo_name))) + diff --git a/rhodecode/model/repo_permission.py b/rhodecode/model/repo_permission.py new file mode 100644 --- /dev/null +++ b/rhodecode/model/repo_permission.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.model.users_group + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + repository permission model for RhodeCode + + :created_on: Oct 1, 2011 + :author: nvinot + :copyright: (C) 2011-2011 Nicolas Vinot + :license: GPLv3, see COPYING for more details. +""" +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging +from rhodecode.model.db import BaseModel, RepoToPerm, Permission +from rhodecode.model.meta import Session + +log = logging.getLogger(__name__) + +class RepositoryPermissionModel(BaseModel): + def get_user_permission(self, repository, user): + return RepoToPerm.query() \ + .filter(RepoToPerm.user == user) \ + .filter(RepoToPerm.repository == repository) \ + .scalar() + + def update_user_permission(self, repository, user, permission): + permission = Permission.get_by_key(permission) + current = self.get_user_permission(repository, user) + if current: + if not current.permission is permission: + current.permission = permission + else: + p = RepoToPerm() + p.user = user + p.repository = repository + p.permission = permission + Session.add(p) + Session.commit() + + def delete_user_permission(self, repository, user): + current = self.get_user_permission(repository, user) + if current: + Session.delete(current) + Session.commit() + + def update_or_delete_user_permission(self, repository, user, permission): + if permission: + self.update_user_permission(repository, user, permission) + else: + self.delete_user_permission(repository, user) diff --git a/rhodecode/model/repos_group.py b/rhodecode/model/repos_group.py --- a/rhodecode/model/repos_group.py +++ b/rhodecode/model/repos_group.py @@ -127,8 +127,8 @@ class ReposGroupModel(BaseModel): try: repos_group = Group.get(repos_group_id) old_path = repos_group.full_path - - #change properties + + # change properties repos_group.group_description = form_data['group_description'] repos_group.parent_group = Group.get(form_data['group_parent_id']) repos_group.group_name = repos_group.get_new_name(form_data['group_name']) diff --git a/rhodecode/model/user.py b/rhodecode/model/user.py --- a/rhodecode/model/user.py +++ b/rhodecode/model/user.py @@ -49,7 +49,6 @@ PERM_WEIGHTS = {'repository.none': 0, class UserModel(BaseModel): - def get(self, user_id, cache=False): user = self.sa.query(User) if cache: @@ -87,6 +86,7 @@ class UserModel(BaseModel): new_user.api_key = generate_api_key(form_data['username']) self.sa.add(new_user) self.sa.commit() + return new_user except: log.error(traceback.format_exc()) self.sa.rollback() @@ -96,6 +96,7 @@ class UserModel(BaseModel): """ Checks if user is in database, if not creates this user marked as ldap user + :param username: :param password: :param user_dn: @@ -386,3 +387,4 @@ class UserModel(BaseModel): repository.repo_name] = p return user + diff --git a/rhodecode/model/users_group.py b/rhodecode/model/users_group.py new file mode 100644 --- /dev/null +++ b/rhodecode/model/users_group.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.model.users_group + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + users group model for RhodeCode + + :created_on: Oct 1, 2011 + :author: nvinot + :copyright: (C) 2011-2011 Nicolas Vinot + :license: GPLv3, see COPYING for more details. +""" +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging +import traceback + +from rhodecode.model import BaseModel +from rhodecode.model.caching_query import FromCache +from rhodecode.model.db import UsersGroupMember, UsersGroup + +log = logging.getLogger(__name__) + +class UsersGroupModel(BaseModel): + + def get(self, users_group_id, cache = False): + users_group = UsersGroup.query() + if cache: + users_group = users_group.options(FromCache("sql_cache_short", + "get_users_group_%s" % users_group_id)) + return users_group.get(users_group_id) + + def get_by_name(self, name, cache = False, case_insensitive = False): + users_group = UsersGroup.query() + if case_insensitive: + users_group = users_group.filter(UsersGroup.users_group_name.ilike(name)) + else: + users_group = users_group.filter(UsersGroup.users_group_name == name) + if cache: + users_group = users_group.options(FromCache("sql_cache_short", + "get_users_group_%s" % name)) + return users_group.scalar() + + def create(self, form_data): + try: + new_users_group = UsersGroup() + for k, v in form_data.items(): + setattr(new_users_group, k, v) + + self.sa.add(new_users_group) + self.sa.commit() + return new_users_group + except: + log.error(traceback.format_exc()) + self.sa.rollback() + raise + + def add_user_to_group(self, users_group, user): + for m in users_group.members: + u = m.user + if u.user_id == user.user_id: + return m + + try: + users_group_member = UsersGroupMember() + users_group_member.user = user + users_group_member.users_group = users_group + + users_group.members.append(users_group_member) + user.group_member.append(users_group_member) + + self.sa.add(users_group_member) + self.sa.commit() + return users_group_member + except: + log.error(traceback.format_exc()) + self.sa.rollback() + raise diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_add.html b/rhodecode/templates/admin/repos_groups/repos_groups_add.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_add.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups_add.html @@ -29,7 +29,7 @@
- +
${h.text('group_name',class_='medium')} @@ -38,7 +38,7 @@
- +
${h.textarea('group_description',cols=23,rows=5,class_="medium")} @@ -47,7 +47,7 @@
- +
${h.select('group_parent_id','',c.repo_groups,class_="medium")} diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html @@ -29,7 +29,7 @@
- +
${h.text('group_name',class_='medium')} @@ -38,7 +38,7 @@
- +
${h.textarea('group_description',cols=23,rows=5,class_="medium")} @@ -47,7 +47,7 @@
- +
${h.select('group_parent_id','',c.repo_groups,class_="medium")} diff --git a/rhodecode/templates/admin/settings/settings.html b/rhodecode/templates/admin/settings/settings.html --- a/rhodecode/templates/admin/settings/settings.html +++ b/rhodecode/templates/admin/settings/settings.html @@ -34,7 +34,7 @@
${h.checkbox('destroy',True)} -
@@ -56,12 +56,12 @@
- +
${h.checkbox('full_index',True)} - +
@@ -100,7 +100,7 @@
- +
${h.text('rhodecode_ga_code',size=30)} @@ -124,7 +124,7 @@
- +
@@ -136,7 +136,7 @@
- +
${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'))} diff --git a/rhodecode/templates/admin/users/user_add.html b/rhodecode/templates/admin/users/user_add.html --- a/rhodecode/templates/admin/users/user_add.html +++ b/rhodecode/templates/admin/users/user_add.html @@ -44,7 +44,16 @@ ${h.password('password',class_='small')}
- + +
+
+ +
+
+ ${h.password('password_confirmation',class_="small",autocomplete="off")} +
+
+
diff --git a/rhodecode/templates/admin/users/user_edit.html b/rhodecode/templates/admin/users/user_edit.html --- a/rhodecode/templates/admin/users/user_edit.html +++ b/rhodecode/templates/admin/users/user_edit.html @@ -68,7 +68,16 @@ ${h.password('new_password',class_='medium',autocomplete="off")}
- + +
+
+ +
+
+ ${h.password('password_confirmation',class_="medium",autocomplete="off")} +
+
+
@@ -132,7 +141,7 @@
- +
${h.checkbox('create_repo_perm',value=True)} diff --git a/rhodecode/templates/admin/users/user_edit_my_account.html b/rhodecode/templates/admin/users/user_edit_my_account.html --- a/rhodecode/templates/admin/users/user_edit_my_account.html +++ b/rhodecode/templates/admin/users/user_edit_my_account.html @@ -57,7 +57,16 @@ ${h.password('new_password',class_="medium",autocomplete="off")}
- + +
+
+ +
+
+ ${h.password('password_confirmation',class_="medium",autocomplete="off")} +
+
+
@@ -154,10 +163,12 @@ %endfor %else: +
${_('No repositories yet')} %if h.HasPermissionAny('hg.admin','hg.create.repository')(): - ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'))} + ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-button-small")} %endif +
%endif diff --git a/rhodecode/templates/admin/users_groups/users_group_edit.html b/rhodecode/templates/admin/users_groups/users_group_edit.html --- a/rhodecode/templates/admin/users_groups/users_group_edit.html +++ b/rhodecode/templates/admin/users_groups/users_group_edit.html @@ -253,7 +253,7 @@
- +
${h.checkbox('create_repo_perm',value=True)} diff --git a/rhodecode/templates/settings/repo_settings.html b/rhodecode/templates/settings/repo_settings.html --- a/rhodecode/templates/settings/repo_settings.html +++ b/rhodecode/templates/settings/repo_settings.html @@ -34,7 +34,14 @@ ${h.text('repo_name',class_="small")}
- +
+
+ +
+
+ ${h.select('repo_group','',c.repo_groups,class_="medium")} +
+
diff --git a/rhodecode/tests/__init__.py b/rhodecode/tests/__init__.py --- a/rhodecode/tests/__init__.py +++ b/rhodecode/tests/__init__.py @@ -77,8 +77,6 @@ class TestController(TestCase): self.assertEqual(response.session['rhodecode_user'].username, username) return response.follow() - - def checkSessionFlash(self, response, msg): self.assertTrue('flash' in response.session) self.assertTrue(msg in response.session['flash'][0][1]) diff --git a/rhodecode/tests/functional/test_admin_settings.py b/rhodecode/tests/functional/test_admin_settings.py --- a/rhodecode/tests/functional/test_admin_settings.py +++ b/rhodecode/tests/functional/test_admin_settings.py @@ -137,6 +137,7 @@ class TestAdminSettingsController(TestCo params=dict(_method='put', username='test_admin', new_password=new_password, + password_confirmation = new_password, password='', name=new_name, lastname=new_lastname, @@ -160,6 +161,7 @@ class TestAdminSettingsController(TestCo _method='put', username='test_admin', new_password=old_password, + password_confirmation = old_password, password='', name=old_name, lastname=old_lastname, @@ -186,6 +188,7 @@ class TestAdminSettingsController(TestCo _method='put', username='test_admin', new_password='test12', + password_confirmation = 'test122', name='NewName', lastname='NewLastname', email=new_email,)) @@ -201,6 +204,7 @@ class TestAdminSettingsController(TestCo _method='put', username='test_admin', new_password='test12', + password_confirmation = 'test122', name='NewName', lastname='NewLastname', email=new_email,)) diff --git a/rhodecode/tests/functional/test_admin_users.py b/rhodecode/tests/functional/test_admin_users.py --- a/rhodecode/tests/functional/test_admin_users.py +++ b/rhodecode/tests/functional/test_admin_users.py @@ -16,12 +16,14 @@ class TestAdminUsersController(TestContr self.log_user() username = 'newtestuser' password = 'test12' + password_confirmation = password name = 'name' lastname = 'lastname' email = 'mail@mail.com' response = self.app.post(url('users'), {'username':username, 'password':password, + 'password_confirmation':password_confirmation, 'name':name, 'active':True, 'lastname':lastname, @@ -90,6 +92,7 @@ class TestAdminUsersController(TestContr response = self.app.post(url('users'), {'username':username, 'password':password, + 'password_confirmation':password, 'name':name, 'active':True, 'lastname':lastname, diff --git a/rhodecode/tests/test_hg_operations.py b/rhodecode/tests/test_hg_operations.py --- a/rhodecode/tests/test_hg_operations.py +++ b/rhodecode/tests/test_hg_operations.py @@ -48,7 +48,8 @@ from rhodecode.tests import TESTS_TMP_PA from rhodecode.config.environment import load_environment rel_path = dn(dn(dn(os.path.abspath(__file__)))) -conf = appconfig('config:development.ini', relative_to=rel_path) + +conf = appconfig('config:%s' % sys.argv[1], relative_to=rel_path) load_environment(conf.global_conf, conf.local_conf) add_cache(conf) @@ -56,10 +57,13 @@ add_cache(conf) USER = 'test_admin' PASS = 'test12' HOST = '127.0.0.1:5000' -DEBUG = True if sys.argv[1:] else False +DEBUG = False print 'DEBUG:', DEBUG log = logging.getLogger(__name__) +engine = engine_from_config(conf, 'sqlalchemy.db1.') +init_model(engine) +sa = meta.Session class Command(object): @@ -96,22 +100,15 @@ def test_wrapp(func): return res return __wrapp -def get_session(): - engine = engine_from_config(conf, 'sqlalchemy.db1.') - init_model(engine) - sa = meta.Session - return sa - def create_test_user(force=True): print '\tcreating test user' - sa = get_session() - user = sa.query(User).filter(User.username == USER).scalar() + user = User.get_by_username(USER) if force and user is not None: print '\tremoving current user' - for repo in sa.query(Repository).filter(Repository.user == user).all(): + for repo in Repository.query().filter(Repository.user == user).all(): sa.delete(repo) sa.delete(user) sa.commit() @@ -134,9 +131,8 @@ def create_test_user(force=True): def create_test_repo(force=True): from rhodecode.model.repo import RepoModel - sa = get_session() - user = sa.query(User).filter(User.username == USER).scalar() + user = User.get_by_username(USER) if user is None: raise Exception('user not found') @@ -156,22 +152,17 @@ def create_test_repo(force=True): def set_anonymous_access(enable=True): - sa = get_session() - user = sa.query(User).filter(User.username == 'default').one() - sa.expire(user) + user = User.get_by_username('default') user.active = enable sa.add(user) sa.commit() - sa.remove() - import time;time.sleep(3) print '\tanonymous access is now:', enable - + if enable != User.get_by_username('default').active: + raise Exception('Cannot set anonymous access') def get_anonymous_access(): - sa = get_session() - obj1 = sa.query(User).filter(User.username == 'default').one() - sa.expire(obj1) - return obj1.active + user = User.get_by_username('default') + return user.active #============================================================================== @@ -378,16 +369,15 @@ def test_push_wrong_path(): @test_wrapp def get_logs(): - sa = get_session() - return len(sa.query(UserLog).all()) + return UserLog.query().all() @test_wrapp def test_logs(initial): - sa = get_session() - logs = sa.query(UserLog).all() - operations = 7 - if initial + operations != len(logs): - raise Exception("missing number of logs %s vs %s" % (initial, len(logs))) + logs = UserLog.query().all() + operations = 4 + if len(initial) + operations != len(logs): + raise Exception("missing number of logs initial:%s vs current:%s" % \ + (len(initial), len(logs))) if __name__ == '__main__': @@ -395,18 +385,17 @@ if __name__ == '__main__': create_test_repo() initial_logs = get_logs() + print 'initial activity logs: %s' % len(initial_logs) -# test_push_modify_file() + #test_push_modify_file() test_clone_with_credentials() test_clone_wrong_credentials() - test_push_new_file(commits=2, with_clone=True) test_clone_anonymous() test_push_wrong_path() - test_push_wrong_credentials() test_logs(initial_logs)