def __init__(self, req): self.req = req self.__cache = AuthenticationCache.instance() self.__auth = Authentication() self.parse_user_and_pw() self.options = req.get_options() self._environment_key = None self._user = None
def getAvatarUrl(self, size=40): from multiproject.core.auth.auth import Authentication auth = Authentication() if auth.has_external_profile(self): return conf.external_avatar_url + self.username + "&size=" + str(size) elif self.icon: return conf.url_home_path + "/usericon?username="******"/images/no_icon.gif"
def _do_login(self, req): """ Logs user in Works so that first it is checked that user have inserted correct password. If yes, then create cookie Cookie contains random hex entropy that is also stored into database When user next time requests page data in cookie and db should match """ if req.method != 'POST': return self._show_login(req) remote_user = req.args.get('username') password = req.args.get('password') # If not params if not remote_user or not password: return self._show_login(req) # Try authentication auth = Authentication() auth_username = auth.authenticate(remote_user, password) # User is already logged in if req.authname == auth_username: add_notice(req, _('User is already logged in as %s.' % req.authname)) return req.redirect(self.env.home_href()) # Authentication failed if not auth_username: # Load user for salt userstore = get_userstore() user = userstore.getUser(remote_user) if not user: add_notice(req, _('Incorrect username or password - please try again')) return self._show_login(req) # Provide password reset link for local users that have an author if userstore.is_local(user) and userstore.get_user_author(user): token = get_token(self.env, user) reset_link = tag.a('request new password', href=req.href('/user/pwreset', username=remote_user, token=token)) add_notice(req, Markup("Incorrect username or password - please try again or %s" % reset_link)) else: add_notice(req, _("Incorrect username or password - please try again")) return self._show_login(req) return self._select_login_action(req, auth_username)
def get_preference_panels(self, req): """ Give name of the panel """ user = get_userstore().getUser(req.authname) has_external_avatar = Authentication().has_external_profile(user) if req.authname != 'anonymous' and not has_external_avatar: yield ('image', 'Face image')
def add_user_to_group(self, user_name, group_name, validate=True): """ Adds user to group. :param str user_name: User name :param str group_name: Group name :param bool validate: Validate the permission group has no too much or little permissions :raises InvalidPermissionsState: If cannot add user :raises DatabaseError: Query failure """ # Test if we can add the user ug = self.get_all_user_groups() + [(user_name, group_name)] if validate: self.is_valid_group_members(user_groups=ug) userstore = get_userstore() user = userstore.getUser(user_name) if not user: from multiproject.core.auth.auth import Authentication auth = Authentication() auth.sync_user(user_name) user = userstore.getUser(user_name) if not user: raise InvalidPermissionsState('Unknown user %s' % user_name) # Create group if it doesn't exist group_name = group_name.encode('utf-8') group_id = self.get_group_id(group_name) if group_id is None: conf.log.info('Creating permission group: %s' % group_name) self.create_group(group_name) group_id = self.get_group_id(group_name) self._cache.clear_user_groups(self.trac_environment_key) with admin_query() as cursor: try: cursor.callproc("add_user_to_group", [user.id, group_id]) # User already exists in the group except MySQLdb.IntegrityError: conf.log.warning('User %s already exists in group: %s' % (user_name, group_name)) self.follow_project(user_name, group_name)
def add_user_to_group(self, user_name, group_name): """ Adds user to group. Updates the published time of the project accordingly. :param str user_name: User name :param str group_name: Group name :raises InvalidPermissionsState: If cannot add user :raises DatabaseError: Query failure """ # Test if we can add the user ug = self.get_all_user_groups() + [(user_name, group_name)] self.is_valid_group_members(user_groups=ug) userstore = get_userstore() user = userstore.getUser(user_name) if not user: from multiproject.core.auth.auth import Authentication auth = Authentication() auth.sync_user(user_name) user = userstore.getUser(user_name) if not user: raise InvalidPermissionsState('Unknown user %s' % user_name) # Create group if it doesn't exist group_name = group_name.encode('utf-8') group_id = self.get_group_id(group_name) if group_id is None: self.create_group(group_name) group_id = self.get_group_id(group_name) self._cache.clear_user_groups(self.trac_environment_key) with admin_query() as cursor: cursor.callproc("add_user_to_group", [user.id, group_id]) self._update_published_time()
def authenticate(self, req): """ Does basic authentication for the HTTP client if ``auth=basic`` is set :param Request req: Trac request :return: Username if authentication succeeds, or None (also if this component does not authenticate) """ # Return None: Not to be handled by this component if req.args.get('auth') != 'basic': return None # Check for existing session if req.remote_user: return req.remote_user # Parse username and password from basic authentication request # NOTE: user may or may not be logged in at this point username, password = self._parse_username_password(req) # Challenge for basic auth if username == 'anonymous': req.send_header('WWW-Authenticate', 'Basic realm="Authenticate"') req.send('Authentication required', content_type='text', status=401) return None # Try authentication auth = Authentication() be_username = auth.authenticate(username, password) # Authentication failed if not be_username: req.send_header('WWW-Authenticate', 'Basic realm="Authenticate"') req.send('Authentication failure', status=401) return None self.log.debug('Authenticated user %s with basic auth: %s' % (be_username, username)) return be_username
class BasicAccessControl(object): """ Base class for creating access control with basic authentication If only authentication check is needed, can be used directly If permission check is needed, make a subclass and implement the needed methods """ def __init__(self, req): self.req = req self.__cache = AuthenticationCache.instance() self.__auth = Authentication() self.parse_user_and_pw() self.options = req.get_options() self._environment_key = None self._user = None def parse_user_and_pw(self): self._username = "******" self._password = "" if 'Authorization' in self.req.headers_in: # Authorization: Basic <base64 username and pw> authz_hdr = base64.b64decode( self.req.headers_in['Authorization'].split(' ')[1]) user_data, pw_data = authz_hdr.split(':', 1) self._username = urllib.unquote(user_data) encoded_password = urllib.unquote(pw_data) resulting_password = '' try: resulting_password = encoded_password.decode('UTF-8') except UnicodeDecodeError as e: try: resulting_password = encoded_password.decode('ISO-8859-1') except UnicodeDecodeError as e: # Should not happen, since for ISO-8859-1, every string # is a valid string conf.log.error( 'Password conversion from ISO-8859-1 FAILED') self._password = resulting_password self.req.user = self.username # ??? if not self._username: self._username = "******" self._password = "" def is_authentic(self): """ Check that user really exists """ username = self.username plain_pw = self.plain_pw # Anonymous is always authentic auth_anonymous = 'auth_anonymous' in self.options if auth_anonymous and username == 'anonymous': return True # Compare database hash and computed hash hash = hashlib.sha1(plain_pw).hexdigest() auth_username = self.__cache.getAuthentication(username, hash) if not auth_username: auth_username = self.__auth.authenticate(username, plain_pw) self.__cache.setAuthentication(username, hash, auth_username) is_auth = False if auth_username is not None: self._username = auth_username auth_encoded = auth_username.encode('utf-8') # Don't know why req.username is set self.req.username = auth_encoded # At least in git.py, req.user was set to be the username self.req.user = auth_encoded is_auth = True return is_auth def has_permission(self): identifier = self.environment_identifier() if not identifier: conf.log.warning("Failed reading identifier") return False # We need environment for permission check, get it .. albeit ugly # TODO: this probably has performance hit as environment is not loaded with caching! if identifier == conf.sys_home_project_name: env = HomeProject().get_env() else: proj = Project.get(env_name=identifier) env = proj.get_env() env.log.debug('basic_access checking has_permission %s for %s' % (self.required_action, self.username)) perms = env[PermissionSystem].get_user_permissions(self.username) return perms.get(self.required_action, False) def is_blocked(self): """ Check if user account is blocked/expired or not. In a case of anonymous user, it's never blocked. .. NOTE: Uses internally the self.username :returns: True if blocked (inactive, banned, disabled or expired) """ # Anonymous user is never blocked if self.username == 'anonymous': return False user = self.user if user.expires and user.expires <= datetime.utcnow(): return True if user.status not in [User.STATUS_ACTIVE]: return True return False def is_allowed_scheme(self, system): if not conf.use_protocol_check: return True protos = ProtocolManager(self.environment_key) scheme = self.req.construct_url('/').split(':')[0] return protos.is_protocol_allowed(scheme, system) @property def required_action(self): if self.is_read(): return self.read_action() return self.write_action() @property def environment_key(self): if self._environment_key is not None: return self._environment_key self._environment_key = env_id(self.environment_identifier()) return self._environment_key @property def plain_pw(self): return self._password @property def username(self): return self._username @property def user(self): if self._user is None: user = get_userstore().getUser(self.username) self._user = user return self._user # Helper for subclasses def parse_identifier_from_uri(self): """ Tries to parse the project identifier from request URI. Example URI values: - /dav/projectx - /dav/projectx/file.txt - /dav/projectx/sub/folder/file.txt .. IMPORTANT:: Check also for /dav/haxor.txt :returns: Project identifier or None """ identifier = None # url_projects_path is ``/`` right-stripped base_projects_url = conf.url_projects_path + '/' # Drop leading slash so we'll have: items[0] == 'dav' if self.req.uri.startswith(base_projects_url): uri = self.req.uri[len(base_projects_url):] else: # TODO: Why just not return None? uri = self.req.uri items = uri.split('/', 3) # Strip empty elements items = [item for item in items if item] # If less than 2 elements, no identifier cannot be found if len(items) < 2: conf.log.warning( 'Failed to parse project identifier from URI: %s' % uri) return None identifier = items[0] if identifier == 'git' or identifier == 'hg' or identifier == 'svn': identifier = items[1] return identifier # # Abstract methods for subclasses that needs # permission checking # def environment_identifier(self): """ Subclass needs to implement this """ raise NotImplementedError def is_read(self): """ Subclass needs to implement this """ raise NotImplementedError def read_action(self): """ Subclass needs to implement this """ raise NotImplementedError def write_action(self): """ Subclass needs to implement this """ raise NotImplementedError
def _add_user(self, req, group_store, membership, username=None): """ :param req: Request :param group_store: CQDEUserGroupStore :param membership: Membership API :param username: Override username from the request """ req.perm.require("PERMISSION_GRANT") if username is None: username = req.args.get("member") group = req.args.get("group") if not username or not group: raise TracError("Invalid arguments while adding user") # Get/check if user exists auth = Authentication() username = auth.get_trac_username(username) # check if already exists if username in [e[0] for e in group_store.get_all_user_groups() if e[1] == group]: add_warning(req, _("User %(user)s already exists in the group %(group)s", group=group, user=username)) return # User does not yet exists in multiproject database => retrieve and create user from authentication backend(s) if not username: # Create user using authentication backends and sync functionality if not auth.sync_user(username): # Show warning with possibility to create a local user - if user has enough permissions. # Otherwise give traditional user cannot be found error home_perm = PermissionCache(env=self.conf.home_env, username=req.authname) if "USER_CREATE" in home_perm: link = Href(self.conf.url_home_path)( "admin/users/create_local", {"goto": req.abs_href(req.path_info)} ) create_link = Markup('<a href="%s">%s</a>' % (link, _("create a local user?"))) add_warning( req, _('User "%(username)s" can not be found. Check name or ', username=username) + create_link ) else: add_warning( req, _('User "%(username)s" can not be found. Please check the name.', username=username) ) return add_notice(req, _("Added user %s to service" % username)) # Now, retrieve the username again username = auth.get_trac_username(username) # when adding to normal project, accept possible membership requests if membership is not None: # If adding user in group it means that membership is accepted if username in membership.get_membership_requests(): membership.accept_membership(username) add_notice(req, _("Membership request has been accepted for %(who)s.", who=username)) try: group_store.add_user_to_group(username, group) add_notice(req, _("User %(who)s has been added to group %(where)s.", who=username, where=group)) except InvalidPermissionsState, e: add_warning( req, _( "User %(who)s cannot be added to group %(where)s. %(reason)s", who=username, where=group, reason=e.message, ), )
class BasicAccessControl(object): """ Base class for creating access control with basic authentication If only authentication check is needed, can be used directly If permission check is needed, make a subclass and implement the needed methods """ def __init__(self, req): self.req = req self.__cache = AuthenticationCache.instance() self.__auth = Authentication() self.parse_user_and_pw() self.options = req.get_options() self._environment_key = None self._user = None def parse_user_and_pw(self): self._username = "******" self._password = "" if 'Authorization' in self.req.headers_in: # Authorization: Basic <base64 username and pw> authz_hdr = base64.b64decode(self.req.headers_in['Authorization'].split(' ')[1]) user_data, pw_data = authz_hdr.split(':',1) self._username = urllib.unquote(user_data) encoded_password = urllib.unquote(pw_data) resulting_password = '' try: resulting_password = encoded_password.decode('UTF-8') except UnicodeDecodeError as e: try: resulting_password = encoded_password.decode('ISO-8859-1') except UnicodeDecodeError as e: # Should not happen, since for ISO-8859-1, every string # is a valid string conf.log.error('Password conversion from ISO-8859-1 FAILED') self._password = resulting_password self.req.user = self.username if not self._username: self._username = "******" self._password = "" def is_authentic(self): """ Check that user really exists """ username = self.username plain_pw = self.plain_pw # Anonymous is always authentic auth_anonymous = 'auth_anonymous' in self.options if auth_anonymous and username == 'anonymous': return True # Compare database hash and computed hash hash = hashlib.sha1(plain_pw).hexdigest() auth_username = self.__cache.getAuthentication(username, hash) if not auth_username: auth_username = self.__auth.authenticate(username, plain_pw) self.__cache.setAuthentication(username, hash, auth_username) is_auth = False if auth_username is not None: self._username = auth_username auth_encoded = auth_username.encode('utf-8') # Don't know why req.username is set self.req.username = auth_encoded # At least in git.py, req.user was set to be the username self.req.user = auth_encoded is_auth = True return is_auth def has_permission(self): identifier = self.environment_identifier() if not identifier: conf.log.warning("Failed reading identifier") return False # We need environment for permission check, get it .. albeit ugly # TODO: this probably has performance hit as environment is not loaded with caching! if identifier == conf.sys_home_project_name: env = HomeProject().get_env() else: proj = Project.get(env_name=identifier) env = proj.get_env() env.log.debug('basic_access checking has_permission %s for %s' % (self.required_action, self.username)) perms = env[PermissionSystem].get_user_permissions(self.username) return perms.get(self.required_action, False) def is_blocked(self): """ Check if user account is blocked/expired or not. In a case of anonymous user, it's never blocked. .. NOTE: Uses internally the self.username :returns: True if blocked (inactive, banned, disabled or expired) """ # Anonymous user is never blocked if self.username == 'anonymous': return False user = self.user if user.expires and user.expires <= datetime.utcnow(): return True if user.status not in [User.STATUS_ACTIVE]: return True return False def is_allowed_scheme(self, system): if not conf.use_protocol_check: return True protos = ProtocolManager(self.environment_key) scheme = self.req.construct_url('/').split(':')[0] return protos.is_protocol_allowed(scheme, system) @property def required_action(self): if self.is_read(): return self.read_action() return self.write_action() @property def environment_key(self): if self._environment_key is not None: return self._environment_key self._environment_key = env_id(self.environment_identifier()) return self._environment_key @property def plain_pw(self): return self._password @property def username(self): return self._username @property def user(self): if self._user is None: user = get_userstore().getUser(self.username) self._user = user return self._user # Helper for subclasses def parse_identifier_from_uri(self): """ Tries to parse the project identifier from request URI. Example URI values: - /dav/projectx - /dav/projectx/file.txt - /dav/projectx/sub/folder/file.txt .. IMPORTANT:: Check also for /dav/haxor.txt :returns: Project identifier or None """ identifier = None # url_projects_path is ``/`` right-stripped base_projects_url = conf.url_projects_path + '/' # Drop leading slash so we'll have: items[0] == 'dav' if self.req.uri.startswith(base_projects_url): uri = self.req.uri[len(base_projects_url):] else: # TODO: Why just not return None? uri = self.req.uri items = uri.split('/', 3) # Strip empty elements items = [item for item in items if item] # If less than 2 elements, no identifier cannot be found if len(items) < 2: conf.log.warning('Failed to parse project identifier from URI: %s' % uri) return None # Identifier is always expected to be second (index 0) element. # If identifier contains the multirepo separator (default: '.'), return the first part identifier = items[1] if conf.multirepo_separator in identifier: identifier = identifier.split(conf.multirepo_separator)[0] return identifier # # Abstract methods for subclasses that needs # permission checking # def environment_identifier(self): """ Subclass needs to implement this """ raise NotImplementedError def is_read(self): """ Subclass needs to implement this """ raise NotImplementedError def read_action(self): """ Subclass needs to implement this """ raise NotImplementedError def write_action(self): """ Subclass needs to implement this """ raise NotImplementedError
def _add_user(self, req, group_store, membership, username=None): """ :param req: Request :param group_store: CQDEUserGroupStore :param membership: Membership API :param username: Override username from the request """ req.perm.require('PERMISSION_GRANT') if username is None: username = req.args.get('member') group = req.args.get('group') if not username or not group: raise TracError('Invalid arguments while adding user') # Get/check if user exists auth = Authentication() username = auth.get_trac_username(username) # check if already exists if username in [ e[0] for e in group_store.get_all_user_groups() if e[1] == group ]: add_warning( req, _('User %(user)s already exists in the group %(group)s', group=group, user=username)) return # User does not yet exists in multiproject database => retrieve and create user from authentication backend(s) if not username: username = req.args.get('member') # Create user using authentication backends and sync functionality if not auth.sync_user(username): # Show warning with possibility to create a local user - if user has enough permissions. # Otherwise give traditional user cannot be found error home_perm = PermissionCache(env=self.conf.home_env, username=req.authname) if 'USER_CREATE' in home_perm: link = Href(self.conf.url_home_path)( 'admin/users/create_local', { 'goto': req.abs_href(req.path_info) }) create_link = Markup('<a href="%s">%s</a>' % (link, _('create a local user?'))) add_warning( req, _('User "%(username)s" can not be found. Check name or ', username=username) + create_link) else: add_warning( req, _('User "%(username)s" can not be found. Please check the name.', username=username)) return add_notice(req, _('Added user %s to service' % username)) # Now, retrieve the username again username = auth.get_trac_username(username) # when adding to normal project, accept possible membership requests if membership is not None: # If adding user in group it means that membership is accepted if username in membership.get_membership_requests(): membership.accept_membership(username) add_notice( req, _('Membership request has been accepted for %(who)s.', who=username)) try: group_store.add_user_to_group(username, group) add_notice( req, _('User %(who)s has been added to group %(where)s.', who=username, where=group)) except InvalidPermissionsState, e: add_warning( req, _('User %(who)s cannot be added to group %(where)s. %(reason)s', who=username, where=group, reason=e.message))