Ejemplo n.º 1
0
 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
Ejemplo n.º 2
0
    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"
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
 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')
Ejemplo n.º 5
0
 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
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
    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()
Ejemplo n.º 8
0
    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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
    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,
                ),
            )
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
    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))