# -*- coding: utf-8 -*-
"""
rhodecode.controllers.login
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Login controller for rhodeocode
:created_on: Apr 22, 2010
:author: marcink
:copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
: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 <http://www.gnu.org/licenses/>.
import logging
import formencode
from formencode import htmlfill
from pylons.i18n.translation import _
from pylons.controllers.util import abort, redirect
from pylons import request, response, session, tmpl_context as c, url
import rhodecode.lib.helpers as h
from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
from rhodecode.lib.base import BaseController, render
from rhodecode.model.db import User
from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
from rhodecode.model.user import UserModel
from rhodecode.model.meta import Session
log = logging.getLogger(__name__)
class LoginController(BaseController):
def __before__(self):
super(LoginController, self).__before__()
def index(self):
# redirect if already logged in
c.came_from = request.GET.get('came_from', None)
if self.rhodecode_user.is_authenticated \
and self.rhodecode_user.username != 'default':
return redirect(url('home'))
if request.POST:
# import Login Form validator class
login_form = LoginForm()
try:
c.form_result = login_form.to_python(dict(request.POST))
# form checks for username/password, now we're authenticated
username = c.form_result['username']
user = User.get_by_username(username, case_insensitive=True)
auth_user = AuthUser(user.user_id)
auth_user.set_authenticated()
cs = auth_user.get_cookie_store()
session['rhodecode_user'] = cs
session.save()
log.info('user %s is now authenticated and stored in '
'session, session attrs %s' % (username, cs))
user.update_lastlogin()
if c.came_from:
return redirect(c.came_from)
else:
except formencode.Invalid, errors:
return htmlfill.render(
render('/login.html'),
defaults=errors.value,
errors=errors.error_dict or {},
prefix_error=False,
encoding="UTF-8")
return render('/login.html')
@HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
'hg.register.manual_activate')
def register(self):
user_model = UserModel()
c.auto_active = False
for perm in User.get_by_username('default').user_perms:
if perm.permission.permission_name == 'hg.register.auto_activate':
c.auto_active = True
break
register_form = RegisterForm()()
form_result = register_form.to_python(dict(request.POST))
form_result['active'] = c.auto_active
user_model.create_registration(form_result)
h.flash(_('You have successfully registered into rhodecode'),
category='success')
Session().commit()
return redirect(url('login_home'))
render('/register.html'),
return render('/register.html')
def password_reset(self):
password_reset_form = PasswordResetForm()()
form_result = password_reset_form.to_python(dict(request.POST))
user_model.reset_password_link(form_result)
h.flash(_('Your password reset link was sent'),
render('/password_reset.html'),
return render('/password_reset.html')
def password_reset_confirmation(self):
if request.GET and request.GET.get('key'):
user = User.get_by_api_key(request.GET.get('key'))
data = dict(email=user.email)
user_model.reset_password(data)
h.flash(_('Your password reset was successful, '
'new password has been sent to your email'),
except Exception, e:
log.error(e)
return redirect(url('reset_password'))
def logout(self):
del session['rhodecode_user']
log.info('Logging out and setting user as Empty')
redirect(url('home'))
rhodecode.model.db
~~~~~~~~~~~~~~~~~~
Database Models for RhodeCode
:created_on: Apr 08, 2010
import os
import datetime
import traceback
from sqlalchemy import *
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
from beaker.cache import cache_region, region_invalidate
from vcs import get_backend
from vcs.utils.helpers import get_scm
from vcs.exceptions import VCSError
from vcs.utils.lazy import LazyProperty
from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
from rhodecode.lib.exceptions import UsersGroupsAssignedException
from rhodecode.lib.compat import json
from rhodecode.lib.caching_query import FromCache
from rhodecode.model.meta import Base, Session
#==============================================================================
# BASE CLASSES
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)
def default(self, obj):
if hasattr(obj, '__json__'):
return obj.__json__()
return json.JSONEncoder.default(self, obj)
class BaseModel(object):
"""Base Model for all classess
@classmethod
def _get_keys(cls):
"""return column names for this model """
return class_mapper(cls).c.keys()
def get_dict(self):
"""return dict with keys and values corresponding
to this model data """
d = {}
for k in self._get_keys():
d[k] = getattr(self, k)
return d
def get_appstruct(self):
"""return list with keys and values tupples corresponding
l = []
l.append((k, getattr(self, k),))
return l
def populate_obj(self, populate_dict):
"""populate model with data from given populate_dict"""
if k in populate_dict:
setattr(self, k, populate_dict[k])
def query(cls):
return Session().query(cls)
def get(cls, id_):
if id_:
return cls.query().get(id_)
def getAll(cls):
return cls.query().all()
def delete(cls, id_):
obj = cls.query().get(id_)
Session().delete(obj)
class RhodeCodeSetting(Base, BaseModel):
__tablename__ = 'rhodecode_settings'
__table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
_app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
def __init__(self, k='', v=''):
self.app_settings_name = k
self.app_settings_value = v
@validates('_app_settings_value')
def validate_settings_value(self, key, val):
assert type(val) == unicode
return val
@hybrid_property
def app_settings_value(self):
v = self._app_settings_value
if v == 'ldap_active':
v = str2bool(v)
return v
@app_settings_value.setter
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)
def __repr__(self):
return "<%s('%s:%s')>" % (self.__class__.__name__,
self.app_settings_name, self.app_settings_value)
def get_by_name(cls, ldap_key):
return cls.query()\
.filter(cls.app_settings_name == ldap_key).scalar()
def get_app_settings(cls, cache=False):
ret = cls.query()
if cache:
ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
if not ret:
raise Exception('Could not get application settings !')
settings = {}
for each in ret:
settings['rhodecode_' + each.app_settings_name] = \
each.app_settings_value
return settings
def get_ldap_settings(cls, cache=False):
ret = cls.query()\
.filter(cls.app_settings_name.startswith('ldap_')).all()
fd = {}
for row in ret:
fd.update({row.app_settings_name:row.app_settings_value})
return fd
class RhodeCodeUi(Base, BaseModel):
__tablename__ = 'rhodecode_ui'
__table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
HOOK_UPDATE = 'changegroup.update'
HOOK_REPO_SIZE = 'changegroup.repo_size'
HOOK_PUSH = 'pretxnchangegroup.push_logger'
HOOK_PULL = 'preoutgoing.pull_logger'
ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
def get_by_key(cls, key):
return cls.query().filter(cls.ui_key == key)
def get_builtin_hooks(cls):
q = cls.query()
q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
cls.HOOK_REPO_SIZE,
cls.HOOK_PUSH, cls.HOOK_PULL]))
return q.all()
def get_custom_hooks(cls):
q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
q = q.filter(cls.ui_section == 'hooks')
def create_or_update_hook(cls, key, val):
new_ui = cls.get_by_key(key).scalar() or cls()
new_ui.ui_section = 'hooks'
new_ui.ui_active = True
new_ui.ui_key = key
new_ui.ui_value = val
Session().add(new_ui)
class User(Base, BaseModel):
__tablename__ = 'users'
__table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
active = Column("active", Boolean(), nullable=True, unique=None, default=None)
admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
user_log = relationship('UserLog', cascade='all')
user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
repositories = relationship('Repository')
user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
group_member = relationship('UsersGroupMember', cascade='all')
notifications = relationship('UserNotification',)
@property
def full_name(self):
return '%s %s' % (self.name, self.lastname)
def full_contact(self):
return '%s %s <%s>' % (self.name, self.lastname, self.email)
def short_contact(self):
def is_admin(self):
return self.admin
return "<%s('id:%s:%s')>" % (self.__class__.__name__,
self.user_id, self.username)
def get_by_username(cls, username, case_insensitive=False, cache=False):
if case_insensitive:
q = cls.query().filter(cls.username.ilike(username))
q = cls.query().filter(cls.username == username)
q = q.options(FromCache("sql_cache_short",
"get_user_%s" % username))
return q.scalar()
def get_by_api_key(cls, api_key, cache=False):
q = cls.query().filter(cls.api_key == api_key)
"get_api_key_%s" % api_key))
def get_by_email(cls, email, cache=False):
q = cls.query().filter(cls.email == email)
"get_api_key_%s" % email))
def update_lastlogin(self):
"""Update user lastlogin"""
self.last_login = datetime.datetime.now()
Session().add(self)
log.debug('updated user %s lastlogin', self.username)
class UserLog(Base, BaseModel):
__tablename__ = 'user_logs'
__table_args__ = {'extend_existing':True}
user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
def action_as_day(self):
return datetime.date(*self.action_date.timetuple()[:3])
user = relationship('User')
repository = relationship('Repository')
class UsersGroup(Base, BaseModel):
__tablename__ = 'users_groups'
users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
return '<userGroup(%s)>' % (self.users_group_name)
def get_by_group_name(cls, group_name, cache=False,
case_insensitive=False):
q = cls.query().filter(cls.users_group_name.ilike(group_name))
q = cls.query().filter(cls.users_group_name == group_name)
"get_user_%s" % group_name))
def get(cls, users_group_id, cache=False):
users_group = cls.query()
users_group = users_group.options(FromCache("sql_cache_short",
"get_users_group_%s" % users_group_id))
return users_group.get(users_group_id)
def create(cls, form_data):
new_users_group = cls()
for k, v in form_data.items():
setattr(new_users_group, k, v)
Session().add(new_users_group)
return new_users_group
except:
log.error(traceback.format_exc())
Session().rollback()
raise
def update(cls, users_group_id, form_data):
users_group = cls.get(users_group_id, cache=False)
if k == 'users_group_members':
users_group.members = []
Session().flush()
members_list = []
if v:
v = [v] if isinstance(v, basestring) else v
for u_id in set(v):
member = UsersGroupMember(users_group_id, u_id)
members_list.append(member)
setattr(users_group, 'members', members_list)
setattr(users_group, k, v)
Session().add(users_group)
def delete(cls, users_group_id):
# check if this group is not assigned to repo
assigned_groups = UsersGroupRepoToPerm.query()\
.filter(UsersGroupRepoToPerm.users_group_id ==
users_group_id).all()
if assigned_groups:
raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
assigned_groups)
Session().delete(users_group)
class UsersGroupMember(Base, BaseModel):
__tablename__ = 'users_groups_members'
users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
user = relationship('User', lazy='joined')
users_group = relationship('UsersGroup')
def __init__(self, gr_id='', u_id=''):
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)
return ugm
class Repository(Base, BaseModel):
__tablename__ = 'repositories'
__table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
private = Column("private", Boolean(), nullable=True, unique=None, default=None)
enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
fork = relationship('Repository', remote_side=repo_id)
group = relationship('RepoGroup')
repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
stats = relationship('Statistics', cascade='all', uselist=False)
followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
logs = relationship('UserLog', cascade='all')
self.repo_id, self.repo_name)
def url_sep(cls):
return '/'
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))
return q.one()
def get_repo_forks(cls, repo_id):
return cls.query().filter(Repository.fork_id == repo_id)
def base_path(cls):
Returns base path when all repos are stored
:param cls:
q = Session().query(RhodeCodeUi)\
.filter(RhodeCodeUi.ui_key == cls.url_sep())
q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
return q.one().ui_value
def just_name(self):
return self.repo_name.split(Repository.url_sep())[-1]
def groups_with_parents(self):
groups = []
if self.group is None:
return groups
cur_gr = self.group
groups.insert(0, cur_gr)
while 1:
gr = getattr(cur_gr, 'parent_group', None)
cur_gr = cur_gr.parent_group
if gr is None:
groups.insert(0, gr)
def groups_and_repo(self):
return self.groups_with_parents, self.just_name
@LazyProperty
def repo_path(self):
Returns base full path for that repository means where it actually
exists on a filesystem
q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
Repository.url_sep())
def repo_full_path(self):
p = [self.repo_path]
# we need to split the name by / since this is how we store the
# names in the database, but that eventually needs to be converted
# into a valid system path
p += self.repo_name.split(Repository.url_sep())
return os.path.join(*p)
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 []
return Repository.url_sep().join(path_prefix + [repo_name])
def _ui(self):
Creates an db based ui object for this repository
from mercurial import ui
from mercurial import config
baseui = ui.ui()
#clean the baseui object
baseui._ocfg = config.config()
baseui._ucfg = config.config()
baseui._tcfg = config.config()
ret = RhodeCodeUi.query()\
.options(FromCache("sql_cache_short", "repository_repo_ui")).all()
hg_ui = ret
for ui_ in hg_ui:
if ui_.ui_active:
log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
ui_.ui_key, ui_.ui_value)
baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
return baseui
def is_valid(cls, repo_name):
returns True if given repo name is a valid filesystem repository
@param cls:
@param repo_name:
from rhodecode.lib.utils import is_valid_repo
return is_valid_repo(repo_name, cls.base_path())
#==========================================================================
# SCM PROPERTIES
def get_changeset(self, rev):
return get_changeset_safe(self.scm_instance, rev)
def tip(self):
return self.get_changeset('tip')
def author(self):
return self.tip.author
def last_change(self):
return self.scm_instance.last_change
# SCM CACHE INSTANCE
def invalidate(self):
return CacheInvalidation.invalidate(self.repo_name)
def set_invalidate(self):
set a cache for invalidation for this instance
CacheInvalidation.set_invalidate(self.repo_name)
@@ -789,450 +793,451 @@ class RepoGroup(Base, BaseModel):
def children(self):
return RepoGroup.query().filter(RepoGroup.parent_group == self)
def name(self):
return self.group_name.split(RepoGroup.url_sep())[-1]
def full_path(self):
return self.group_name
def full_path_splitted(self):
return self.group_name.split(RepoGroup.url_sep())
def repositories(self):
return Repository.query().filter(Repository.group == self)
def repositories_recursive_count(self):
cnt = self.repositories.count()
def children_count(group):
cnt = 0
for child in group.children:
cnt += child.repositories.count()
cnt += children_count(child)
return cnt
return cnt + children_count(self)
def get_new_name(self, group_name):
returns new full group name based on parent and new name
path_prefix = (self.parent_group.full_path_splitted if
self.parent_group else [])
return RepoGroup.url_sep().join(path_prefix + [group_name])
class Permission(Base, BaseModel):
__tablename__ = 'permissions'
permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
self.permission_id, self.permission_name)
return cls.query().filter(cls.permission_name == key).scalar()
def get_default_perms(cls, default_user_id, cache=True):
q = Session().query(UserRepoToPerm, Repository, cls)\
.join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
.join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
.filter(UserRepoToPerm.user_id == default_user_id)
q = q.options(FromCache("sql_cache_short", "get_default_perms"))
class UserRepoToPerm(Base, BaseModel):
__tablename__ = 'repo_to_perm'
__table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
permission = relationship('Permission')
def create(cls, user, repository, permission):
n = cls()
n.user = user
n.repository = repository
n.permission = permission
Session().add(n)
return n
return '<user:%s => %s >' % (self.user, self.repository)
class UserToPerm(Base, BaseModel):
__tablename__ = 'user_to_perm'
__table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
permission = relationship('Permission', lazy='joined')
def has_perm(cls, user_id, perm):
if not isinstance(perm, Permission):
raise Exception('perm needs to be an instance of Permission class')
return cls.query().filter(cls.user_id == user_id)\
.filter(cls.permission == perm).scalar() is not None
def grant_perm(cls, user_id, perm):
new = cls()
new.user_id = user_id
new.permission = perm
Session().add(new)
def revoke_perm(cls, user_id, perm):
obj = cls.query().filter(cls.user_id == user_id)\
.filter(cls.permission == perm).one()
class UsersGroupRepoToPerm(Base, BaseModel):
__tablename__ = 'users_group_repo_to_perm'
__table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
def create(cls, users_group, repository, permission):
n.users_group = users_group
return '<userGroup:%s => %s >' % (self.users_group, self.repository)
class UsersGroupToPerm(Base, BaseModel):
__tablename__ = 'users_group_to_perm'
def has_perm(cls, users_group_id, perm):
return cls.query().filter(cls.users_group_id ==
users_group_id)\
.filter(cls.permission == perm)\
.scalar() is not None
def grant_perm(cls, users_group_id, perm):
new.users_group_id = users_group_id
def revoke_perm(cls, users_group_id, perm):
obj = cls.query().filter(cls.users_group_id == users_group_id)\
class UserRepoGroupToPerm(Base, BaseModel):
__tablename__ = 'group_to_perm'
__table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
class UsersGroupRepoGroupToPerm(Base, BaseModel):
__tablename__ = 'users_group_repo_group_to_perm'
users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
class Statistics(Base, BaseModel):
__tablename__ = 'statistics'
__table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
repository = relationship('Repository', single_parent=True)
class UserFollowing(Base, BaseModel):
__tablename__ = 'user_followings'
__table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
UniqueConstraint('user_id', 'follows_user_id')
, {'extend_existing':True})
user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
follows_repository = relationship('Repository', order_by='Repository.repo_name')
def get_repo_followers(cls, repo_id):
return cls.query().filter(cls.follows_repo_id == repo_id)
class CacheInvalidation(Base, BaseModel):
__tablename__ = 'cache_invalidation'
__table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
def __init__(self, cache_key, cache_args=''):
self.cache_key = cache_key
self.cache_args = cache_args
self.cache_active = False
self.cache_id, self.cache_key)
def invalidate(cls, key):
Returns Invalidation object if this given key should be invalidated
None otherwise. `cache_active = False` means that this cache
state is not valid and needs to be invalidated
:param key:
.filter(CacheInvalidation.cache_key == key)\
.filter(CacheInvalidation.cache_active == False)\
.scalar()
def set_invalidate(cls, key):
Mark this Cache key for invalidation
log.debug('marking %s for invalidation' % key)
inv_obj = Session().query(cls)\
.filter(cls.cache_key == key).scalar()
if inv_obj:
inv_obj.cache_active = False
log.debug('cache key not found in invalidation db -> creating one')
inv_obj = CacheInvalidation(key)
Session().add(inv_obj)
except Exception:
def set_valid(cls, key):
Mark this cache key as active and currently cached
inv_obj = CacheInvalidation.query()\
.filter(CacheInvalidation.cache_key == key).scalar()
inv_obj.cache_active = True
class ChangesetComment(Base, BaseModel):
__tablename__ = 'changeset_comments'
__table_args__ = ({'extend_existing':True},)
comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
revision = Column('revision', String(40), nullable=False)
line_no = Column('line_no', Unicode(10), nullable=True)
f_path = Column('f_path', Unicode(1000), nullable=True)
user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
text = Column('text', Unicode(25000), nullable=False)
modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
author = relationship('User', lazy='joined')
repo = relationship('Repository')
def get_users(cls, revision):
Returns user associated with this changesetComment. ie those
who actually commented
:param revision:
return Session().query(User)\
.filter(cls.revision == revision)\
.join(ChangesetComment.author).all()
class Notification(Base, BaseModel):
__tablename__ = 'notifications'
__table_args__ = ({'extend_existing':True})
TYPE_CHANGESET_COMMENT = u'cs_comment'
TYPE_MESSAGE = u'message'
TYPE_MENTION = u'mention'
TYPE_REGISTRATION = u'registration'
notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
subject = Column('subject', Unicode(512), nullable=True)
body = Column('body', Unicode(50000), nullable=True)
created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
type_ = Column('type', Unicode(256))
created_by_user = relationship('User')
notifications_to_users = relationship('UserNotification', lazy='joined',
cascade="all, delete, delete-orphan")
def recipients(self):
return [x.user for x in UserNotification.query()\
.filter(UserNotification.notification == self).all()]
def create(cls, created_by, subject, body, recipients, type_=None):
if type_ is None:
type_ = Notification.TYPE_MESSAGE
notification = cls()
notification.created_by_user = created_by
notification.subject = subject
notification.body = body
notification.type_ = type_
notification.created_on = datetime.datetime.now()
for u in recipients:
assoc = UserNotification()
assoc.notification = notification
u.notifications.append(assoc)
Session().add(notification)
return notification
def description(self):
from rhodecode.model.notification import NotificationModel
return NotificationModel().make_description(self)
class UserNotification(Base, BaseModel):
__tablename__ = 'user_to_notification'
__table_args__ = (UniqueConstraint('user_id', 'notification_id'),
{'extend_existing':True})
user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
read = Column('read', Boolean, default=False)
sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
user = relationship('User', lazy="joined")
notification = relationship('Notification', lazy="joined",
order_by=lambda:Notification.created_on.desc(),
cascade='all')
def mark_as_read(self):
self.read = True
class DbMigrateVersion(Base, BaseModel):
__tablename__ = 'db_migrate_version'
repository_id = Column('repository_id', String(250), primary_key=True)
repository_path = Column('repository_path', Text)
version = Column('version', Integer)
rhodecode.model.notification
~~~~~~~~~~~~~~
Model for notifications
:created_on: Nov 20, 2011
import rhodecode
from rhodecode.lib import helpers as h
from rhodecode.model import BaseModel
from rhodecode.model.db import Notification, User, UserNotification
class NotificationModel(BaseModel):
def __get_user(self, user):
if isinstance(user, basestring):
return User.get_by_username(username=user)
return self._get_instance(User, user)
def __get_notification(self, notification):
if isinstance(notification, Notification):
elif isinstance(notification, int):
return Notification.get(notification)
if notification:
raise Exception('notification must be int or Instance'
' of Notification got %s' % type(notification))
def create(self, created_by, subject, body, recipients,
type_=Notification.TYPE_MESSAGE):
def create(self, created_by, subject, body, recipients=None,
type_=Notification.TYPE_MESSAGE, with_email=True,
email_kwargs={}):
Creates notification of given type
:param created_by: int, str or User instance. User who created this
notification
:param subject:
:param body:
:param recipients: list of int, str or User objects
:param recipients: list of int, str or User objects, when None
is given send to all admins
:param type_: type of notification
:param with_email: send email with this notification
:param email_kwargs: additional dict to pass as args to email template
from rhodecode.lib.celerylib import tasks, run_task
if not getattr(recipients, '__iter__', False):
if recipients and not getattr(recipients, '__iter__', False):
raise Exception('recipients must be a list of iterable')
created_by_obj = self.__get_user(created_by)
recipients_objs = []
obj = self.__get_user(u)
if obj:
recipients_objs.append(obj)
recipients_objs = set(recipients_objs)
if recipients:
# empty recipients means to all admins
recipients_objs = User.query().filter(User.admin == True).all()
notif = Notification.create(created_by=created_by_obj, subject=subject,
body=body, recipients=recipients_objs,
type_=type_)
if with_email is False:
return notif
# send email with notification
for rec in recipients_objs:
email_subject = NotificationModel().make_description(notif, False)
type_ = EmailNotificationModel.TYPE_CHANGESET_COMMENT
type_ = type_
email_body = body
kwargs = {'subject':subject, 'body':h.rst(body)}
kwargs.update(email_kwargs)
email_body_html = EmailNotificationModel()\
.get_email_tmpl(type_, **{'subject':subject,
'body':h.rst(body)})
.get_email_tmpl(type_, **kwargs)
run_task(tasks.send_email, rec.email, email_subject, email_body,
email_body_html)
def delete(self, user, notification):
# we don't want to remove actual notification just the assignment
notification = self.__get_notification(notification)
user = self.__get_user(user)
if notification and user:
obj = UserNotification.query()\
.filter(UserNotification.user == user)\
.filter(UserNotification.notification
== notification)\
.one()
self.sa.delete(obj)
return True
def get_for_user(self, user):
return user.notifications
def get_unread_cnt_for_user(self, user):
return UserNotification.query()\
.filter(UserNotification.read == False)\
.filter(UserNotification.user == user).count()
def get_unread_for_user(self, user):
return [x.notification for x in UserNotification.query()\
.filter(UserNotification.user == user).all()]
def get_user_notification(self, user, notification):
.filter(UserNotification.notification == notification)\
.filter(UserNotification.user == user).scalar()
def make_description(self, notification, show_age=True):
Creates a human readable description based on properties
of notification object
_map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
notification.TYPE_MESSAGE:_('sent message'),
notification.TYPE_MENTION:_('mentioned you')}
notification.TYPE_MENTION:_('mentioned you'),
notification.TYPE_REGISTRATION:_('registered in RhodeCode')}
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
tmpl = "%(user)s %(action)s %(when)s"
if show_age:
when = h.age(notification.created_on)
DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
when = DTF(notification.created_on)
data = dict(user=notification.created_by_user.username,
action=_map[notification.type_],
when=when)
return tmpl % data
class EmailNotificationModel(BaseModel):
TYPE_CHANGESET_COMMENT = 'changeset_comment'
TYPE_PASSWORD_RESET = 'passoword_link'
TYPE_REGISTRATION = 'registration'
TYPE_DEFAULT = 'default'
def __init__(self):
self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
self.email_types = {
self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
self.TYPE_REGISTRATION:'email_templates/registration.html',
self.TYPE_DEFAULT:'email_templates/default.html'
}
def get_email_tmpl(self, type_, **kwargs):
return generated template for email based on given type
:param type_:
base = self.email_types.get(type_, self.TYPE_DEFAULT)
email_template = self._tmpl_lookup.get_template(base)
# translator inject
_kwargs = {'_':_}
_kwargs.update(kwargs)
log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
return email_template.render(**_kwargs)
rhodecode.model.user
~~~~~~~~~~~~~~~~~~~~
users model for RhodeCode
:created_on: Apr 9, 2010
from pylons import url
from rhodecode.lib import safe_unicode
from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
Notification
from rhodecode.lib.exceptions import DefaultUserException, \
UserOwnsReposException
from sqlalchemy.exc import DatabaseError
from rhodecode.lib import generate_api_key
from sqlalchemy.orm import joinedload
PERM_WEIGHTS = {'repository.none': 0,
'repository.read': 1,
'repository.write': 3,
'repository.admin': 3}
class UserModel(BaseModel):
def get(self, user_id, cache=False):
user = self.sa.query(User)
user = user.options(FromCache("sql_cache_short",
"get_user_%s" % user_id))
return user.get(user_id)
def get_by_username(self, username, cache=False, case_insensitive=False):
user = self.sa.query(User).filter(User.username.ilike(username))
user = self.sa.query(User)\
.filter(User.username == username)
return user.scalar()
def get_by_api_key(self, api_key, cache=False):
return User.get_by_api_key(api_key, cache)
def create(self, form_data):
new_user = User()
setattr(new_user, k, v)
new_user.api_key = generate_api_key(form_data['username'])
self.sa.add(new_user)
self.sa.commit()
return new_user
self.sa.rollback()
def create_or_update(self, username, password, email, name, lastname,
active=True, admin=False, ldap_dn=None):
Creates a new instance if not found, or updates current one
:param username:
:param password:
:param email:
:param active:
:param name:
:param lastname:
:param admin:
:param ldap_dn:
from rhodecode.lib.auth import get_crypt_password
log.debug('Checking for %s account in RhodeCode database', username)
if user is None:
log.debug('creating new user %s', username)
log.debug('updating user %s', username)
new_user = user
new_user.username = username
new_user.admin = admin
new_user.password = get_crypt_password(password)
new_user.api_key = generate_api_key(username)
new_user.email = email
new_user.active = active
new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
new_user.name = name
new_user.lastname = lastname
except (DatabaseError,):
def create_for_container_auth(self, username, attrs):
Creates the given user if it's not already in the database
:param attrs:
if self.get_by_username(username, case_insensitive=True) is None:
# autogenerate email for container account without one
generate_email = lambda usr: '%s@container_auth.account' % usr
new_user.password = None
new_user.email = attrs['email']
new_user.active = attrs.get('active', True)
new_user.name = attrs['name'] or generate_email(username)
new_user.lastname = attrs['lastname']
log.debug('User %s already exists. Skipping creation of account'
' for container auth.', username)
return None
def create_ldap(self, username, password, user_dn, attrs):
Checks if user is in database, if not creates this user marked
as ldap user
:param user_dn:
log.debug('Checking for such ldap account in RhodeCode database')
# autogenerate email for ldap account without one
generate_email = lambda usr: '%s@ldap.account' % usr
username = username.lower()
# add ldap account always lowercase
new_user.email = attrs['email'] or generate_email(username)
new_user.ldap_dn = safe_unicode(user_dn)
new_user.name = attrs['name']
log.debug('this %s user exists skipping creation of ldap account',
username)
def create_registration(self, form_data):
if k != 'admin':
self.sa.flush()
# notification to admins
subject = _('new user registration')
body = ('New user registration\n'
'username: %s\n'
'email: %s\n')
body = body % (form_data['username'], form_data['email'])
'---------------------\n'
'- Username: %s\n'
'- Full Name: %s\n'
'- Email: %s\n')
body = body % (new_user.username, new_user.full_name,
new_user.email)
edit_url = url('edit_user', id=new_user.user_id, qualified=True)
kw = {'registered_user_url':edit_url}
NotificationModel().create(created_by=new_user, subject=subject,
body=body, recipients=None,
type_=Notification.TYPE_REGISTRATION,
email_kwargs=kw)
run_task(tasks.send_email, None,
_('[RhodeCode] New User registration'),
body)
def update(self, user_id, form_data):
user = self.get(user_id, cache=False)
if user.username == 'default':
raise DefaultUserException(
_("You can't Edit this user since it's"
" crucial for entire application"))
if k == 'new_password' and v != '':
user.password = v
user.api_key = generate_api_key(user.username)
setattr(user, k, v)
self.sa.add(user)
def update_my_account(self, user_id, form_data):
if k not in ['admin', 'active']:
def delete(self, user_id):
_("You can't remove this user since it's"
if user.repositories:
raise UserOwnsReposException(_('This user still owns %s '
'repositories and cannot be '
'removed. Switch owners or '
'remove those repositories') \
% user.repositories)
self.sa.delete(user)
def reset_password_link(self, data):
run_task(tasks.send_password_link, data['email'])
def reset_password(self, data):
run_task(tasks.reset_user_password, data['email'])
def fill_data(self, auth_user, user_id=None, api_key=None):
Fetches auth_user by user_id,or api_key if present.
Fills auth_user attributes with those taken from database.
Additionally set's is_authenitated if lookup fails
present in database
:param auth_user: instance of user to set attributes
:param user_id: user id to fetch by
:param api_key: api key to fetch by
if user_id is None and api_key is None:
raise Exception('You need to pass user_id or api_key')
if api_key:
dbuser = self.get_by_api_key(api_key)
dbuser = self.get(user_id)
if dbuser is not None and dbuser.active:
log.debug('filling %s data', dbuser)
for k, v in dbuser.get_dict().items():
setattr(auth_user, k, v)
return False
auth_user.is_authenticated = False
def fill_perms(self, user):
Fills user permission attribute with permissions taken from database
works for permissions given for repositories, and for permissions that
are granted to groups
:param user: user instance to fill his perms
user.permissions['repositories'] = {}
user.permissions['global'] = set()
#======================================================================
# fetch default permissions
default_user = User.get_by_username('default', cache=True)
default_user_id = default_user.user_id
default_perms = Permission.get_default_perms(default_user_id)
if user.is_admin:
#==================================================================
# #admin have all default rights set to admin
user.permissions['global'].add('hg.admin')
for perm in default_perms:
p = 'repository.admin'
user.permissions['repositories'][perm.UserRepoToPerm.
repository.repo_name] = p
# set default permissions
uid = user.user_id
# default global
default_global_perms = self.sa.query(UserToPerm)\
.filter(UserToPerm.user_id == default_user_id)
for perm in default_global_perms:
user.permissions['global'].add(perm.permission.permission_name)
# default for repositories
if perm.Repository.private and not (perm.Repository.user_id ==
uid):
# disable defaults for private repos,
p = 'repository.none'
elif perm.Repository.user_id == uid:
# set admin if owner
p = perm.Permission.permission_name
# overwrite default with user permissions if any
# user global
user_perms = self.sa.query(UserToPerm)\
.options(joinedload(UserToPerm.permission))\
.filter(UserToPerm.user_id == uid).all()
for perm in user_perms:
# user repositories
user_repo_perms = self.sa.query(UserRepoToPerm, Permission,
Repository)\
.join((Repository, UserRepoToPerm.repository_id ==
Repository.repo_id))\
.join((Permission, UserRepoToPerm.permission_id ==
Permission.permission_id))\
.filter(UserRepoToPerm.user_id == uid).all()
for perm in user_repo_perms:
if perm.Repository.user_id == uid:
# check if user is part of groups for this repository and fill in
# (or replace with higher) permissions
# users group global
user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
.options(joinedload(UsersGroupToPerm.permission))\
.join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
UsersGroupMember.users_group_id))\
.filter(UsersGroupMember.user_id == uid).all()
for perm in user_perms_from_users_groups:
# users group repositories
user_repo_perms_from_users_groups = self.sa.query(
UsersGroupRepoToPerm,
Permission, Repository,)\
.join((Repository, UsersGroupRepoToPerm.repository_id ==
.join((Permission, UsersGroupRepoToPerm.permission_id ==
.join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
for perm in user_repo_perms_from_users_groups:
cur_perm = user.permissions['repositories'][perm.
UsersGroupRepoToPerm.
repository.repo_name]
# overwrite permission only if it's greater than permission
# given from other sources
if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
user.permissions['repositories'][perm.UsersGroupRepoToPerm.
return user
new file 100644
## -*- coding: utf-8 -*-
<%inherit file="main.html"/>
A new user have registered in RhodeCode
${body}
View this user here :${registered_user_url}
\ No newline at end of file
Status change: