def testRemoveMember(self): from submin.models import user user.add("testUser1", email="[email protected]", password="******") user.add("testUser2", email="[email protected]", password="******") group.add("testGroup") u1 = user.User("testUser1") u2 = user.User("testUser2") g = group.Group("testGroup") g.add_member(u1) g.add_member(u2) g.remove_member(u2) self.assert_("testUser2" not in g.members())
def external_sync(): """Synchronizes external users""" from submin.models import user errormsgs = [] if options.value('enabled_external', 'no') == 'no': errormsgs.append('external is not enabled') return {'errormsgs': errormsgs, 'success': False} group = LDAPGroup(options.value('external_passwd'), options.value('external_user')) if not group: errormsgs.append('cannot connect to LDAP server') return {'errormsgs': errormsgs, 'success': False} group_members = group.members if not group_members: errormsgs.append('cannot find LDAP group or its members') return {'errormsgs': errormsgs, 'success': False} user_list = user.list(user.FakeAdminUser()) for username in group_members: email = group_members[username]['email'] fullname = group_members[username]['fullname'] if not validate_username(username): errormsgs.append(InvalidUsername(username)) continue if not validate_email(email): errormsgs.append(InvalidEmail(email)) continue if not validate_fullname(fullname): errormsgs.append(InvalidFullname(fullname)) fullname = username if username not in user_list: # A new user user.add(username=username, email=email, send_mail=False) user.User(username).fullname = fullname else: u = user.User(username) # Update fullname and email if necessary if (u.email, u.fullname) != (email, fullname): u.email = email u.fullname = fullname return {'errormsgs': errormsgs, 'success': True}
def listGroupUsers(self, req, g): members = list(g.members()) asking_user = user.User(req.session['user']['name']) if asking_user.is_admin: nonmembers = [] usernames = user.list(asking_user) for username in usernames: if username not in members: nonmembers.append(username) return XMLTemplateResponse("ajax/groupmembers.xml", { "members": members, "nonmembers": nonmembers, "group": g.name }) if asking_user.name not in g.members(): return XMLStatusResponse( 'listGroupUsers', False, "You do not have permission to view this group.") return XMLTemplateResponse("ajax/groupmembers.xml", { "members": members, "nonmembers": [], "group": g.name })
def write_groups(self, config): from submin.models import group from submin.models import user # get filename authz_file = config.get('svn', 'authz_file') # read file cp = self.read_ini(authz_file) # get groups groups = cp.options('groups') for groupname in groups: members = [ x.strip() for x in cp.get('groups', groupname).split(',') ] try: g = group.add(groupname) except GroupExistsError: g = group.Group(groupname) for member in members: u = user.User(member) try: g.add_member(u) except MemberExistsError: pass if groupname == "submin-admins": u.is_admin = True
def listUserGroups(self, req, u): member_of_names = list(u.member_of()) session_user = req.session['user'] if session_user['is_admin']: asking_user = user.User(session_user['name']) nonmember_of = [] groupnames = group.list(asking_user) for groupname in groupnames: if groupname not in member_of_names: nonmember_of.append(groupname) return XMLTemplateResponse( "ajax/usermemberof.xml", { "memberof": member_of_names, "nonmemberof": nonmember_of, "user": u.name }) if session_user['name'] != u.name: return XMLStatusResponse( 'listUserGroups', False, "You do not have permission to " "view this user.") return XMLTemplateResponse("ajax/usermemberof.xml", { "memberof": member_of_names, "nonmemberof": [], "user": u.name })
def saveNotifications(self, req, u): session_user = req.session['user'] notifications_str = req.post.get('saveNotifications').split('|') notifications = [] for n_str in notifications_str: try: type_repos, enabled = n_str.split(',', 1) vcstype, reposname = type_repos.split(':', 1) except ValueError: return XMLStatusResponse( 'saveNotifications', False, 'Badly formatted notifications, reload the page and try again' ) enabled = (enabled == "true") notifications.append({ 'name': reposname, 'vcs': vcstype, 'enabled': enabled }) try: asking_user = user.User(session_user['name']) u.set_notifications(notifications, asking_user) except UserPermissionError as e: return XMLStatusResponse('saveNotifications', False, str(e)) return XMLStatusResponse("saveNotifications", True, "Saved notifications for user " + u.name)
def getpermissions(self, req, repos): session_user = req.session['user'] asking_user = user.User(session_user['name']) path = req.post.get('getPermissions') branch_or_path = Path(path) if not repos.has_path_permissions: branch_or_path = branch_or_path.lstrip('/') perms = permissions.list_by_path(repos.name, repos.vcs_type, path) usernames = [] if 'userlist' in req.post: usernames = user.list(asking_user) groupnames = [] if 'grouplist' in req.post: groupnames = group.list(asking_user) templatevars = { 'perms': perms, 'repository': repos.name, 'path': branch_or_path, 'usernames': usernames, 'groupnames': groupnames } return XMLTemplateResponse('ajax/repositoryperms.xml', templatevars)
def export_notifications(**kwargs): """Export a mailer.py config file For each user/repository pair, a config group is created. Only if a user has read or read/write permission to one or multiple paths in that repository, _and_ if the user has notifications enabled for that repository, _and_ if the user has a non-empty email-address. Multiple paths are grouped together by a regexp group (multiple|paths)""" bindir = options.static_path("hooks") + 'svn' # get a list of all users from submin.models import user users = [user.User(name) for name in user.list(user.FakeAdminUser())] groups = [] for u in users: if not u.email: continue u_notif = u.notifications() for repos in u_notif: repos_path = str(options.env_path("svn_dir") + repos) if not u_notif[repos]["enabled"]: continue # strip leading / paths = [ x[1:] for x in permissions.list_readable_user_paths(repos, "svn", u) ] if len(paths) == 0: continue elif len(paths) == 1: for_paths = paths[0] elif len(paths) > 0: for_paths = "(" + "|".join(paths) + ")" # Only match complete path, not partial paths (ticket #257) repos_path_re = '^' + repos_path + '$' g = { "repos_name": repos, "for_repos": repos_path_re, "email": u.email, "for_paths": for_paths, "username": u.name } groups.append(g) email = options.value( 'commit_email_from', 'Please configure commit_email_from <*****@*****.**>') templatevariables = {"groups": groups, 'from_addr': email} from submin.template.shortcuts import evaluate content = evaluate("plugins/vcs/svn/mailer.conf", templatevariables) filename = str((options.env_path() + 'conf') + 'mailer.py.conf') file(filename, 'w').writelines(content.encode('utf-8'))
def write_users(self, config): from submin.models import user # get filename htpasswd_file = config.get('svn', 'access_file') userprop_file = config.get('svn', 'userprop_file') # read files htpasswd = open(htpasswd_file).readlines() userprop = self.read_ini(userprop_file) from submin.models.user import FakeAdminUser # fake an admin user fake_admin = FakeAdminUser() # add users for line in htpasswd: (username, md5_password) = line.strip('\n').split(':') try: # This is a hack. We need to supply an email-address and # if we don't supply a password, user.add() will try to send # an email. Both email and password will be set later. u = user.add(username, email="[email protected]", password=md5_password, origin='submin2-admin') except UserExistsError: u = user.User(username) u.set_md5_password(md5_password) if userprop.has_section(username): if userprop.has_option(username, 'email'): u.email = userprop.get(username, 'email') if userprop.has_option(username, 'notifications_allowed'): allowed = userprop.get(username, 'notifications_allowed') allowed = [x.strip() for x in allowed.split(',')] enabled = [] if userprop.has_option(username, 'notifications_enabled'): enabled = userprop.get(username, 'notifications_enabled') enabled = [x.strip() for x in enabled.split(',')] repositories = [] for repos in allowed: repos_enabled = False if repos in enabled: repos_enabled = True repositories.append({ 'name': repos, 'vcs': 'svn', # since Submin 1.2 only support svn 'enabled': repos_enabled }) u.set_notifications(repositories, fake_admin)
def testAddMember(self): from submin.models import user user.add("testUser", email="[email protected]", password="******") group.add("testGroup") u = user.User("testUser") g = group.Group("testGroup") g.add_member(u) self.assert_("testUser" in g.members())
def handler(self, request, path): if 'auto_authenticate' in request.session: username = request.session['auto_authenticate'] else: if not request.post: return self.evaluate_form(session=request.session) username = request.post.get('username', '') password = request.post.get('password', '') if '' in (username, password): return self.evaluate_form(session=request.session) invalid_login = True u = None try: u = user.User(username) invalid_login = False except UnknownUserError as e: pass if 'auto_authenticate' in request.session: del request.session['auto_authenticate'] request.session['change_password_hint'] = True else: try: if not u or not u.check_password(password): return self.evaluate_form( 'Not a valid username and password combination') except NoMD5PasswordError as e: return self.evaluate_form(str(e)) if invalid_login: return self.evaluate_form( 'Not a valid username and password combination') if not database_isuptodate(): localvalues = {} request.session['upgrade_user'] = True base_url = options.url_path('base_url_submin') localvalues['base_url'] = str(base_url) localvalues['session_user'] = u return Response(evaluate('database_upgrade.html', localvalues)) url = options.url_path('base_url_submin') if 'redirected_from' in request.session: login_url = options.url_path('base_url_submin') + 'login' url = request.session['redirected_from'] if url.startswith(login_url): url = options.url_path('base_url_submin') session_user = u.session_object() session_user['is_authenticated'] = True request.session['user'] = session_user request.session.cleanup() return Redirect(url, request, store_url=False)
def ajaxhandler(self, req, path): username = '' if len(path) < 2: return XMLStatusResponse('', False, 'Invalid path') action = path[0] username = path[1] if action == 'delete': return self.removeUser(req, username) u = user.User(username) if 'fullname' in req.post and req.post.get('fullname').strip(): return self.setFullName(req, u) if 'email' in req.post and req.post.get('email').strip(): return self.setEmail(req, u) if 'password' in req.post and req.post.get('password').strip(): return self.setPassword(req, u) if 'sendPasswordMail' in req.post: return self.sendPasswordMail(req, u) if 'addToGroup' in req.post: return self.addToGroup(req, u) if 'removeFromGroup' in req.post: return self.removeFromGroup(req, u) if 'listUserGroups' in req.post: return self.listUserGroups(req, u) if 'listNotifications' in req.post: return self.listNotifications(req, u) if 'addSSHKey' in req.post: return self.addSSHKey(req, u) if 'removeSSHKey' in req.post: return self.removeSSHKey(req, u) if 'saveNotifications' in req.post: return self.saveNotifications(req, u) if 'listSSHKeys' in req.post: return self.listSSHKeys(req, u) if 'setIsAdmin' in req.post: return self.setIsAdmin(req, u) return XMLStatusResponse('', False, 'Unknown command')
def run(username): if "SSH_ORIGINAL_COMMAND" not in os.environ: print ("No command provided. " \ + "Expected something like git-receive-pack", file=sys.stderr) sys.exit(1) orig_cmd = os.environ["SSH_ORIGINAL_COMMAND"] if not orig_cmd.startswith("git") or orig_cmd[3] not in (' ', '-'): print ("Not a git-command. Expected something like " \ + "git-receive-pack",file=sys.stderr) sys.exit(1) # command is something like `git-receive-pack repo.git' # or `git receive-pack repo.git' cmd, repo = orig_cmd.split(" ", 1) if repo[0] == repo[-1] == "'": repo = repo[1:-1] # git 1.5 adds a slash? if repo[0] == '/': repo = repo[1:] repo = repo[:-4] # remove '.git' sub_cmd = cmd[4:] u = user.User(username) readable_paths = permissions.list_readable_user_paths(repo, "git", u) if not readable_paths: print("Permission denied for %s to user %s" % (repo, u), file=sys.stderr) sys.exit(1) if sub_cmd not in WRITE_CMDS + READ_CMDS: print("Unrecognized command:", cmd, file=sys.stderr) sys.exit(1) elif sub_cmd in WRITE_CMDS: # check if we have write access writeable_paths = permissions.list_writeable_user_paths(repo, "git", u) if not writeable_paths: print ("Permission denied for writing, for %s to user %s" % \ (repo, u),file=sys.stderr) sys.exit(1) # To pass on to the git-hook os.environ["SUBMIN_USERNAME"] = username os.environ["SUBMIN_REPO"] = repo repo_path = repository.directory('git', repo) print("Original command: %s" % orig_cmd, file=sys.stderr) print("executing git-%s '%s'" % (sub_cmd, repo_path), file=sys.stderr) # XXX: retreive git-path from options. os.execvp('git', ['git', 'shell', '-c', "git-%s '%s'" % (sub_cmd, repo_path)])
def addMember(self, req, groupname): username = req.post.get('addMember') success = True try: group.Group(groupname).add_member(user.User(username)) except MemberExistsError: success = False msgs = { True: 'User %s added to group %s' % (username, groupname), False: 'User %s already in group %s' % (username, groupname) } return XMLStatusResponse('addMember', success, msgs[success])
def removeMember(self, req, groupname): g = group.Group(groupname) username = req.post.get('removeMember') success = True try: g.remove_member(user.User(username)) except: success = False msgs = { True: 'User %s removed from group %s' % (username, groupname), False: 'User %s is not a member of group %s' % (username, groupname) } return XMLStatusResponse('removeMember', success, msgs[success])
def export_htpasswd(*args, **kwargs): try: htpasswd_file = options.env_path("htpasswd_file") except UnknownKeyError: return if not os.path.exists(htpasswd_file.dirname()): return with open(htpasswd_file, "w+") as htpasswd: for username in user.list(FakeAdminUser()): u = user.User(username) htpasswd.write("%s:%s\n" % (username, u.get_password_hash())) os.chmod(htpasswd_file, 0o600)
def send_email(self, req, path): templatevars = { 'base_url': options.url_path('base_url_submin') } username = req.post.get('username', '') if username: try: u = user.User(username) u.prepare_password_reset(req.remote_address) templatevars['sent'] = True except UnknownUserError: templatevars['sent'] = True else: templatevars['form'] = True formatted = evaluate('password.html', templatevars) return Response(formatted)
def add(self, req, path, templatevars): base_url = options.url_path('base_url_submin') reposname = '' if req.post and req.post['repository']: import re reposname = req.post.get('repository').strip() if re.findall('[^a-zA-Z0-9_-]', reposname): return self.showAddForm( req, reposname, 'Invalid characters in repository name') if "vcs" not in req.post or req.post.get("vcs").strip() == "": return self.showAddForm( req, reposname, "No repository type selected. Please select a repository type." ) vcs_type = req.post.get("vcs").strip() if reposname == '': return self.showAddForm(req, reposname, 'Repository name not supplied') if vcs_type not in vcs_list(): return self.showAddForm(req, reposname, "Invalid repository type supplied.") try: a = Repository(reposname, vcs_type) return self.showAddForm( req, reposname, 'Repository %s already exists' % reposname) except DoesNotExistError: pass try: asking_user = user.User(req.session['user']['name']) Repository.add(vcs_type, reposname, asking_user) except PermissionError as e: return ErrorResponse('could not create repository', request=req, details=str(e)) url = '%s/repositories/show/%s/%s' % (base_url, vcs_type, reposname) return Redirect(url, req) return self.showAddForm(req, reposname)
def run(): env_path = options.env_path() filename = os.path.expanduser("~/.ssh/authorized_keys") filename = options.value("git_dev_authorized_keysfile", filename) if not os.path.exists(os.path.dirname(filename)): try: # create dir and file if one of them doesn't exist os.mkdir(os.path.dirname(filename)) open(filename, 'a') except OSError as e: if e.errno != errno.EACCES: raise raise Exception( 'Could not write "%s", please check that git user can write it.' % filename) # Make the authorized_keys file only readable to the git-user gituser = options.value("git_user") owner = getpwnam(gituser) os.chown(os.path.dirname(filename), owner.pw_uid, owner.pw_gid) os.chmod(filename, 0o600) www_key_file = env_path + "conf" + "id_dsa.pub" if not www_key_file.exists(): raise Exception( "Could not find the submin ssh-key. Please run submin2-admin git init" ) key_fp = open(str(www_key_file)) www_key = key_fp.readline().strip() key_fp.close() # instead of writing ascii, write utf-8 encoding fp = codecs.open(str(filename), "a+", 'utf-8') env_vars = "PATH='%s' PYTHONPATH='%s'" % \ (options.value("env_path"), ':'.join(sys.path)) fp.write('command="%s submin2-admin \'%s\' git admin" %s\n' % \ (env_vars, env_path, www_key)) userlist = user.list(user.FakeAdminUser()) for x in userlist: u = user.User(x) ssh_keys = u.ssh_keys() if not ssh_keys: continue for ssh_key in ssh_keys: fp.write('command="%s submin2-admin \'%s\' git user %s" %s\n' % \ (env_vars, env_path, u, ssh_key["key"])) fp.close()
def run(reposname): failed, succeeded = [], [] errors = [] if reposname: repositories = [reposname] else: # even though we might know the username, it we can't filter on # username, as permissions might be revoked from a repository # and it won't show up if we use Repositor.list() (because it is # revoked). So regenerate for all repositories repositories = [ x['name'] for x in repository.Repository.list_all() if x['vcs'] == 'git' ] # get a list of all users + their notifications as tuples: (u, n) # We get the notifications straight away, otherwise we have to # call it user * repositories times, and the u.notifications() # function can be heavy on the database if called that many times # (for a couple of users and 36 repositories, database was hit # almost 3000 times during profiling) user_notifications = [] for name in user.list(user.FakeAdminUser()): u = user.User(name) if not u.email: # user without emails cannot be notified continue n = u.notifications() user_notifications.append((u, n)) for reposname in repositories: try: update_notification(reposname, user_notifications) except SetGitConfigError as e: errors.append(str(e)) failed.append(reposname) else: succeeded.append(reposname) if len(failed) > 0: total = len(failed) + len(succeeded) msg = "Some repositories failed to update: %s. (%s/%s)" % ( ','.join(failed), len(failed), total) raise UpdateFailed(msg)
def show(self, req, vcs_type, path, templatevars): import os.path u = user.User(req.session['user']['name']) try: repos = Repository(path[0], vcs_type) # Lie if user has no permission to read if not u.is_admin and not repository.userHasReadPermissions( u.name, path[0], vcs_type): raise DoesNotExistError except DoesNotExistError: return ErrorResponse('This repository does not exist.', request=req) trac_enabled = options.value('enabled_trac', 'no') != 'no' if trac_enabled: templatevars['trac_config_ok'] = True templatevars['trac_exists'] = False try: if trac.exists(path[0]): templatevars['trac_exists'] = True except MissingConfig as e: templatevars['trac_config_ok'] = False templatevars['trac_msg'] = \ 'There is something missing in your config: %s' % str(e) trac_base_url = options.url_path('base_url_trac') trac_http_url = str(trac_base_url + repos.name) templatevars['trac_http_url'] = trac_http_url try: vcs_url = repos.url() except MissingConfig as e: vcs_url = "" templatevars['vcs_url_error'] = str(e) templatevars['vcs_url'] = vcs_url templatevars['repository'] = repos templatevars['vcs_type'] = vcs_type formatted = evaluate_main('repositories.html', templatevars, request=req) return Response(formatted)
def removeUser(self, req, username): if username == req.session['user']['name']: return XMLStatusResponse('removeUser', False, 'You are not allowed to delete yourself') try: u = user.User(username) except UnknownUserError: return XMLStatusResponse('removeUser', False, "Could not find user '%s'" % username) try: u.remove() except Exception as e: return XMLStatusResponse( 'removeUser', False, 'User %s not deleted: %s' % (username, str(e))) return XMLStatusResponse('removeUser', True, 'User %s deleted' % username)
def reset_password(self, req, username, key): templatevars = { 'base_url': options.url_path('base_url_submin') } if 'auto_authenticate' in req.session: del req.session['auto_authenticate'] try: u = user.User(username) except UnknownUserError: raise if not u.valid_password_reset_key(key): templatevars['invalid'] = True else: templatevars['reset'] = True req.session['auto_authenticate'] = username u.clear_password_reset_key() formatted = evaluate('password.html', templatevars) return Response(formatted)
def main(): from sys import argv, path import os path.append('_SUBMIN_LIB_DIR_') interpreter = "perl" scriptname = 'commit-email.pl' scriptdir = os.path.dirname(argv[0]) env = 'SUBMIN_LIB_DIR' if env in os.environ: path.append(os.environ[env]) if len(argv) < 4: print "Usage: %s <configfile> <repository path> <revision>" % argv[0] return os.environ['SUBMIN_ENV'] = argv[1] repospath = argv[2] rev = argv[3] from submin.models import storage storage.open() from submin.models import options bindir = options.static_path("hooks") + 'svn' from submin.models import user userlist = [user.User(name) for name in user.list(user.FakeAdminUser())] n = buildNotifications(userlist) repos = os.path.basename(repospath) if repos not in n: print "no such repository" return mailer = bindir + scriptname for email in n[repos]: os.system("%s %s '%s' '%s' -s '[%s]' '%s'" % (interpreter, mailer, repospath, rev, repos, email))
def show(self, req, path, localvars): if len(path) < 1: return ErrorResponse('Invalid path', request=req) is_admin = req.session['user']['is_admin'] if not is_admin and path[0] != req.session['user']['name']: raise Unauthorized('Permission denied to view this user') try: u = user.User(path[0]) except (IndexError, UnknownUserError): return ErrorResponse('This user does not exist.', request=req) localvars['user'] = u if 'change_password_hint' in req.session: localvars['change_password_hint'] = True p = list(permissions.list_by_user(u.name)) localvars['permissions'] = p localvars['enabled_git'] = 'git' in options.value('vcs_plugins', '') localvars['external'] = u.is_external formatted = evaluate_main('users.html', localvars, request=req) return Response(formatted)
def tracEnvCreate(self, req, repos): asking_user = user.User(req.session['user']['name']) trac.create(repos.vcs_type, repos.name, asking_user) return XMLStatusResponse( 'tracEnvCreate', True, 'Trac environment "%s" (%s) created.' % (repos.name, repos.vcs_type))
(groupname, correct_groupname)) continue except GroupExistsError: verboseprint("Group %s already exists" % correct_groupname) try: correct_group = group.Group(correct_groupname) except UnknownGroupError: correct_group = group.add(correct_groupname) verboseprint("Created group %s" % correct_groupname) old_group = group.Group(groupname) members = list(old_group.members()) for member in members: try: correct_group.add_member(user.User(member)) verboseprint("Added %s to group %s" % (member, correct_groupname)) except MemberExistsError: verboseprint("User %s is already a member of group %s" % (member, correct_groupname)) params = [ permission_by_group['repository'], permission_by_group['vcs'], permission_by_group['path'], correct_groupname, 'group', permission_by_group['permission'] ] try: permissions.add(*params) verboseprint("Added permission for group %s" % correct_groupname) except storage.SQLIntegrityError: