def update_permissions(id, **kwargs): allowed_permissions = ['can_create_users', 'can_create_repositories'] for key in kwargs.keys(): if key not in allowed_permissions: raise KeyError("You can only change these permissions: " + ', '.join(allowed_permissions)) db.execute(users.update().values(**kwargs).where(users.c.user_id == id))
def create_repo(name, owner_id): (name, path) = format_name(name) # TODO: a synchronization problem exists here when: # - the repository already exists, but doesn't appear in the DB # - the repository is missing, but appears in the DB # If one of these conditions exists, what do we do? # # For now: # - if it exists in FS but not DB, populate DB # - if it exists in DB but not FS, initialize repo in FS # # Basically, we naively chug on through and initialize all uninitialized # pieces without throwing exceptions. I'm not sure this is the best # policy, as desyncs here may indicate further consistency issues, and # ignoring them may hamper debugging. # error out if the repository already exists in both FS and DB repo = get_repo(name) if os.path.isdir(path) and repo: raise KeyError("Repository {0} already exists.".format(name)) # if no FS directory exists, initialize the repo /w subprocess'ed git if not os.path.isdir(path): # TODO: can we use a native Python library here instead of subprocess? # TODO: do we need to insure git is installed properly somehow? import subprocess cmd = ['git', 'init', '--bare', path] proc = subprocess.Popen(args=cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = proc.communicate() if proc.returncode != 0: raise Exception("git init {0} failed: {1}".format(path, stderr)) # insert repository row s = repos.insert().values(repository_name=name) result = db.execute(s) repo_id = result.inserted_primary_key[0] # insert default owner ACL # TODO: Shouldn't this be in the same transaction as above?! # Theoretically a failure here could be unrecoverable. s = repo_acls.insert().values( user_id=owner_id, repository_id=repo_id, is_owner=True, can_write=True, can_rewind=True, can_read=True, can_create_tag=True, can_modify_tag=True, ) result = db.execute(s) # make sure all hooks are up-to-date update_hooks(name) return repo_id
def create_repo(name, owner_id): (name, path) = format_name(name) # TODO: a synchronization problem exists here when: # - the repository already exists, but doesn't appear in the DB # - the repository is missing, but appears in the DB # If one of these conditions exists, what do we do? # # For now: # - if it exists in FS but not DB, populate DB # - if it exists in DB but not FS, initialize repo in FS # # Basically, we naively chug on through and initialize all uninitialized # pieces without throwing exceptions. I'm not sure this is the best # policy, as desyncs here may indicate further consistency issues, and # ignoring them may hamper debugging. # error out if the repository already exists in both FS and DB repo = get_repo(name) if os.path.isdir(path) and repo: raise KeyError("Repository {0} already exists.".format(name)) # if no FS directory exists, initialize the repo /w subprocess'ed git if not os.path.isdir(path): # TODO: can we use a native Python library here instead of subprocess? # TODO: do we need to insure git is installed properly somehow? import subprocess cmd = ["git", "init", "--bare", path] proc = subprocess.Popen(args=cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = proc.communicate() if proc.returncode != 0: raise Exception("git init {0} failed: {1}".format(path, stderr)) # insert repository row s = repos.insert().values(repository_name=name) result = db.execute(s) repo_id = result.inserted_primary_key[0] # insert default owner ACL # TODO: Shouldn't this be in the same transaction as above?! # Theoretically a failure here could be unrecoverable. s = repo_acls.insert().values( user_id=owner_id, repository_id=repo_id, is_owner=True, can_write=True, can_rewind=True, can_read=True, can_create_tag=True, can_modify_tag=True, ) result = db.execute(s) # make sure all hooks are up-to-date update_hooks(name) return repo_id
def update_permissions(id, **kwargs): allowed_permissions = [ 'can_create_users', 'can_create_repositories' ] for key in kwargs.keys(): if key not in allowed_permissions: raise KeyError("You can only change these permissions: " + ', '.join(allowed_permissions)) db.execute(users .update() .values(**kwargs) .where(users.c.user_id == id))
def delete_repo_by_id(id): repo = get_repo_by_id(id) if not repo: raise KeyError("Invalid repository ID") (name, path) = format_name(repo['repository_name']) import shutil shutil.rmtree(path) # TODO: Shouldn't these two be in the same transaction? # How do we implement transactions in SQLAlchemy? s = repos.delete().where(repos.c.repository_id == id) result = db.execute(s) s = repo_acls.delete().where(repo_acls.c.repository_id == id) result = db.execute(s)
def update_repo_acls(repo_id, user_id, **kwargs): # check if we need to do an update # TODO: This behavior is ANYTHING except transaction-safe... s = select([repo_acls]).where((repo_acls.c.repository_id == repo_id) & (repo_acls.c.user_id == user_id)) acl = db.execute(s).fetchone() if not acl: s = repo_acls.insert().values(repository_id=repo_id, user_id=user_id, **kwargs) else: s = ( repo_acls.update() .values(**kwargs) .where((repo_acls.c.repository_id == repo_id) & (repo_acls.c.user_id == user_id)) ) db.execute(s)
def delete_repo_by_id(id): repo = get_repo_by_id(id) if not repo: raise KeyError("Invalid repository ID") (name, path) = format_name(repo["repository_name"]) import shutil shutil.rmtree(path) # TODO: Shouldn't these two be in the same transaction? # How do we implement transactions in SQLAlchemy? s = repos.delete().where(repos.c.repository_id == id) result = db.execute(s) s = repo_acls.delete().where(repo_acls.c.repository_id == id) result = db.execute(s)
def update_repo_acls(repo_id, user_id, **kwargs): # check if we need to do an update # TODO: This behavior is ANYTHING except transaction-safe... s = select([repo_acls]).where((repo_acls.c.repository_id == repo_id) & (repo_acls.c.user_id == user_id)) acl = db.execute(s).fetchone() if not acl: s = repo_acls.insert().values(repository_id=repo_id, user_id=user_id, **kwargs) else: s = repo_acls.update().values( **kwargs).where((repo_acls.c.repository_id == repo_id) & (repo_acls.c.user_id == user_id)) db.execute(s)
def change_password(name, password, old_password=None): """ Changes the target user's password to the password given by ``password``. If ``old_password`` is given but does not match the user's current password, a ValueError is raised. """ if not get_user(name): raise KeyError("User '{0}' not found".format(name)) if old_password is not None: if not verify_user(name, password): raise ValueError("Old password was incorrect.") salt = generate_salt() hash = hash_password(password, salt) db.execute(users.update().values( pass_salt=salt, pass_hash=hash).where(users.c.user_name == name))
def regenerate_authorized_keys(): # do as much validaton as possible home = os.getenv('HOME', None) if home is None: raise ValueError("HOME environment variable should be set!") # perform SQL query s = select([keys, users]).where(keys.c.user_id == users.c.user_id) result = db.execute(s) # attempt to locate our SSH wrapper from distutils.spawn import find_executable wrapper_path = find_executable('gitzebo-ssh-wrapper') # make sure ssh directory exists target_dir = os.path.join(home, '.ssh') try: os.makedirs(target_dir) os.chmod(target_dir, 0700) except OSError as e: if e.errno == errno.EEXIST and os.path.isdir(target_dir): pass else: raise # write resultset to file target_path = os.path.join(target_dir, 'authorized_keys') # TODO: eventually allow for DSA keys, too? venv = '' venv_dir = os.getenv('VIRTUAL_ENV', None) if venv_dir: venv = ' VIRTUAL_ENV=' + venv_dir with open(target_path, 'w') as f: for row in db.execute(s): f.write('command="{wrapper} USERNAME={username}{venv}",' 'no-port-forwarding,' 'no-X11-forwarding,' 'no-agent-forwarding,' 'no-pty' ' ssh-rsa {key}\n'.format( wrapper=wrapper_path, username=row['user_name'], key=row['public_key'], venv=venv, # virtualenv spec, if applicable ) )
def change_password(name, password, old_password=None): """ Changes the target user's password to the password given by ``password``. If ``old_password`` is given but does not match the user's current password, a ValueError is raised. """ if not get_user(name): raise KeyError("User '{0}' not found".format(name)) if old_password is not None: if not verify_user(name, password): raise ValueError("Old password was incorrect.") salt = generate_salt() hash = hash_password(password, salt) db.execute(users .update() .values(pass_salt=salt, pass_hash=hash) .where(users.c.user_name == name))
def regenerate_authorized_keys(): # do as much validaton as possible home = os.getenv('HOME', None) if home is None: raise ValueError("HOME environment variable should be set!") # perform SQL query s = select([keys, users]).where(keys.c.user_id == users.c.user_id) result = db.execute(s) # attempt to locate our SSH wrapper from distutils.spawn import find_executable wrapper_path = find_executable('gitzebo-ssh-wrapper') # make sure ssh directory exists target_dir = os.path.join(home, '.ssh') try: os.makedirs(target_dir) os.chmod(target_dir, 0700) except OSError as e: if e.errno == errno.EEXIST and os.path.isdir(target_dir): pass else: raise # write resultset to file target_path = os.path.join(target_dir, 'authorized_keys') # TODO: eventually allow for DSA keys, too? venv = '' venv_dir = os.getenv('VIRTUAL_ENV', None) if venv_dir: venv = ' VIRTUAL_ENV=' + venv_dir with open(target_path, 'w') as f: for row in db.execute(s): f.write('command="{wrapper} USERNAME={username}{venv}",' 'no-port-forwarding,' 'no-X11-forwarding,' 'no-agent-forwarding,' 'no-pty' ' ssh-rsa {key}\n'.format( wrapper=wrapper_path, username=row['user_name'], key=row['public_key'], venv=venv, # virtualenv spec, if applicable ))
def get_key_by_key(key): """ Look up a key by the actual key (reverse lookup). Used to enforce uniqueness of keys, so that we don't generate an invalid authorized_keys file. """ s = select([keys]).where(keys.c.public_key == key) result = db.execute(s) return result.fetchone()
def delete_key(key_id): # delete it! s = keys.delete().where(keys.c.key_id == key_id) result = db.execute(s) if result.rowcount != 1: raise Exception("Deleted {0} rows, expected to delete one.".format( result.rowcount)) # insure the filesystem reflects changes and return regenerate_authorized_keys() return result.rowcount
def get_repos_for_user(user_id): columns = repos.c + [ repo_acls.c.is_owner, repo_acls.c.can_read, repo_acls.c.can_write, repo_acls.c.can_rewind, repo_acls.c.can_create_tag, repo_acls.c.can_modify_tag ] s = select(columns).where( (repo_acls.c.repository_id == repos.c.repository_id) & (repo_acls.c.user_id == user_id) & (repo_acls.c.can_read | repo_acls.c.is_owner)) return db.execute(s).fetchall()
def get_repos_for_user(user_id): columns = repos.c + [ repo_acls.c.is_owner, repo_acls.c.can_read, repo_acls.c.can_write, repo_acls.c.can_rewind, repo_acls.c.can_create_tag, repo_acls.c.can_modify_tag, ] s = select(columns).where( (repo_acls.c.repository_id == repos.c.repository_id) & (repo_acls.c.user_id == user_id) & (repo_acls.c.can_read | repo_acls.c.is_owner) ) return db.execute(s).fetchall()
def create_key(user_id, key, name): # validate data if not validate_key(key): raise ValueError("Invalid public key: '" + key + "'") # validate uniqueness if get_key_by_key(key): raise Exception("Key already exists in database.") # insert key stmt = keys.insert().values(user_id=user_id, name=name, public_key=key) result = db.execute(stmt) # insure the filesystem reflects changes and return regenerate_authorized_keys() return result.inserted_primary_key
def get_repo_acls(repo_id, user_id=None): """ Get a list of the permissions each user has on this repo. If user_id is provided, the returned list will only contain that user's ACLs. """ # branch if filtering by user ID user_clause = True if user_id: user_clause = users.c.user_id == user_id sq = select([repo_acls]).where(repo_acls.c.repository_id == repo_id).alias() columns = [c for c in users.c] columns += [c for c in sq.c if c not in [sq.c.user_id]] s = users.outerjoin(sq, (users.c.user_id == sq.c.user_id)).select(user_clause).with_only_columns(columns) result = db.execute(s) return result.fetchall()
def create_user(name, password, commit_name=None, commit_email=None, can_create_users=False, can_create_repositories=False): salt = generate_salt() hash = hash_password(password, salt) if commit_name is None: commit_name = name if commit_email is None: commit_email = '' stmt = users.insert().values( user_name=name, commit_name=commit_name, commit_email=commit_email, pass_salt=salt, pass_hash=hash, can_create_users=can_create_users, can_create_repositories=can_create_repositories, ) result = db.execute(stmt) return result.inserted_primary_key[0]
def create_key(user_id, key, name): # validate data if not validate_key(key): raise ValueError("Invalid public key: '" + key + "'") # validate uniqueness if get_key_by_key(key): raise Exception("Key already exists in database.") # insert key stmt = keys.insert().values( user_id=user_id, name=name, public_key=key) result = db.execute(stmt) # insure the filesystem reflects changes and return regenerate_authorized_keys() return result.inserted_primary_key
def get_repo_acls(repo_id, user_id=None): """ Get a list of the permissions each user has on this repo. If user_id is provided, the returned list will only contain that user's ACLs. """ # branch if filtering by user ID user_clause = True if user_id: user_clause = users.c.user_id == user_id sq = (select([repo_acls ]).where(repo_acls.c.repository_id == repo_id).alias()) columns = [c for c in users.c] columns += [c for c in sq.c if c not in [sq.c.user_id]] s = (users.outerjoin( sq, (users.c.user_id == sq.c.user_id)).select(user_clause).with_only_columns(columns)) result = db.execute(s) return result.fetchall()
def delete_user(user_id): s = repo_acls.delete().where(repo_acls.c.user_id == user_id) result = db.execute(s) s = users.delete().where(users.c.user_id == user_id) result = db.execute(s)
def get_key_by_id(key_id): s = select([keys]).where(keys.c.key_id == key_id) result = db.execute(s) return result.fetchone()
def get_keys(user_id): s = select([keys]).where(keys.c.user_id == user_id) result = db.execute(s) return result.fetchall()
def get_users(): s = select([users]) result = db.execute(s) return result.fetchall()
def get_repo_by_id(id): s = select([repos]).where(repos.c.repository_id == id) result = db.execute(s) return result.fetchone()
def get_user(name): s = select([users]).where(users.c.user_name == name) result = db.execute(s) return result.fetchone()
def get_user_by_id(id): s = select([users]).where(users.c.user_id == id) result = db.execute(s) return result.fetchone()
def get_repo(name): s = select([repos]).where(repos.c.repository_name == name) result = db.execute(s) return result.fetchone()