def get(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        group_id = kwargs.get("group_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        group = Group.get(self.session, group_id, name)
        if not group:
            return self.notfound()

        # Only members can request permissions
        if not self.current_user.is_member(group.my_members()):
            return self.forbidden()

        args_by_perm = get_grantable_permissions(
            self.session, settings().restricted_ownership_permissions
        )
        dropdown_form, text_form = GroupPermissionRequest._get_forms(args_by_perm, None)

        self.render(
            "group-permission-request.html",
            dropdown_form=dropdown_form,
            text_form=text_form,
            group=group,
            args_by_perm_json=json.dumps(args_by_perm),
            dropdown_help=settings().permission_request_dropdown_help,
            text_help=settings().permission_request_text_help,
        )
Esempio n. 2
0
    def get(self, *args: Any, **kwargs: Any) -> None:
        name = self.get_path_argument("name")

        user = User.get(self.session, name=name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        metadata_key = self.get_path_argument("key")

        if metadata_key == USER_METADATA_SHELL_KEY:
            return self.redirect("/users/{}/shell".format(user.name))
        elif metadata_key == USER_METADATA_GITHUB_USERNAME_KEY:
            return self.redirect("/github/link_begin/{}".format(user.id))

        known_field = metadata_key in settings().metadata_options
        metadata_item = get_user_metadata_by_key(self.session, user.id,
                                                 metadata_key)
        if not metadata_item and not known_field:
            return self.notfound()

        form = UserMetadataForm()
        form.value.choices = settings().metadata_options.get(
            metadata_key, DEFAULT_METADATA_OPTIIONS)

        self.render(
            "user-metadata.html",
            form=form,
            user=user,
            is_enabled=known_field,
            metadata_key=metadata_key,
        )
Esempio n. 3
0
    def get(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        group_id = kwargs.get("group_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        group = Group.get(self.session, group_id, name)
        if not group:
            return self.notfound()

        # Only members can request permissions
        if not self.current_user.is_member(group.my_members()):
            return self.forbidden()

        args_by_perm = get_grantable_permissions(
            self.session,
            settings().restricted_ownership_permissions)
        dropdown_form, text_form = GroupPermissionRequest._get_forms(
            args_by_perm, None)

        self.render(
            "group-permission-request.html",
            dropdown_form=dropdown_form,
            text_form=text_form,
            group=group,
            args_by_perm_json=json.dumps(args_by_perm),
            dropdown_help=settings().permission_request_dropdown_help,
            text_help=settings().permission_request_text_help,
        )
Esempio n. 4
0
    def get_current_user(self):
        # type: () -> Optional[User]
        username = self.request.headers.get(settings().user_auth_header)
        if not username:
            return None

        # Users must be fully qualified
        if not re.match("^{}$".format(USERNAME_VALIDATION), username):
            raise InvalidUser()

        try:
            user, created = User.get_or_create(self.session, username=username)
            if created:
                logging.info("Created new user %s", username)
                self.session.commit()
                # Because the graph doesn't initialize until the updates table
                # is populated, we need to refresh the graph here in case this
                # is the first update.
                self.graph.update_from_db(self.session)
        except sqlalchemy.exc.OperationalError:
            # Failed to connect to database or create user, try to reconfigure the db. This invokes
            # the fetcher to try to see if our URL string has changed.
            Session.configure(bind=get_db_engine(settings().database))
            raise DatabaseFailure()

        # service accounts are, by definition, not interactive users
        if user.is_service_account:
            raise InvalidUser()

        return user
Esempio n. 5
0
 def get(self, *args, **kwargs):
     # type: (*Any, **Any) -> None
     self.render(
         "help.html",
         how_to_get_help=settings().how_to_get_help,
         site_docs=settings().site_docs,
         grant_perm=get_permission(self.session, PERMISSION_GRANT),
         create_perm=get_permission(self.session, PERMISSION_CREATE),
         audit_perm=get_permission(self.session, PERMISSION_AUDITOR),
     )
Esempio n. 6
0
 def get(self, *args: Any, **kwargs: Any) -> None:
     # Redirect the user to GitHub to authorize our app.
     state = binascii.hexlify(os.urandom(16))
     self.set_cookie("github-link-state", state, httponly=True)
     params = {
         "client_id":
         settings().github_app_client_id,
         "state":
         state,
         "redirect_uri":
         "{}/github/link_complete/{}".format(settings().url,
                                             kwargs["user_id"]),
     }
     self.redirect("https://github.com/login/oauth/authorize?" +
                   urlencode(params))
Esempio n. 7
0
    def post(self, *args: Any, **kwargs: Any) -> None:
        name = self.get_path_argument("name")

        user = User.get(self.session, name=name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        metadata_key = self.get_path_argument("key")

        if metadata_key == USER_METADATA_SHELL_KEY:
            return self.redirect("/users/{}/shell".format(user.name))
        elif metadata_key == USER_METADATA_GITHUB_USERNAME_KEY:
            return self.redirect("/github/link_begin/{}".format(user.id))

        known_field = metadata_key in settings().metadata_options
        metadata_item = get_user_metadata_by_key(self.session, user.id,
                                                 metadata_key)
        if not metadata_item and not known_field:
            return self.notfound()

        form = UserMetadataForm(self.request.arguments)
        form.value.choices = settings().metadata_options.get(
            metadata_key, DEFAULT_METADATA_OPTIIONS)
        if not form.validate():
            return self.render(
                "user-metadata.html",
                form=form,
                user=user,
                metadata_key=metadata_key,
                is_enabled=known_field,
                alerts=self.get_form_alerts(form.errors),
            )

        set_user_metadata(self.session, user.id, metadata_key,
                          form.data["value"])

        AuditLog.log(
            self.session,
            self.current_user.id,
            "changed_user_metadata",
            "Changed {}: {}".format(metadata_key, form.data["value"]),
            on_user_id=user.id,
        )

        return self.redirect("/users/{}?refresh=yes".format(user.name))
Esempio n. 8
0
    def _build_form(self, data):
        # type: (Optional[int]) -> Tuple[PermissionRequestForm, Dict[str, List[str]]]
        """Build the permission request form given the request and POST data.

        Normally all fields of the form will be editable.  But if the URL
        locks down a specific value for the group, permission, or argument,
        then the specified fields will display those values and will be
        grayed out and not editable.

        """
        session = self.session
        current_user = self.current_user

        def pairs(seq):
            # type: (Iterable[str]) -> List[Tuple[str, str]]
            return [(item, item) for item in seq]

        form = PermissionRequestForm(data)

        group_names = {
            g.groupname
            for g, e in get_groups_by_user(session, current_user)
        }
        args_by_perm = get_grantable_permissions(
            session,
            settings().restricted_ownership_permissions)
        permission_names = {p for p in args_by_perm}

        group_param = self.get_argument("group", None)
        if group_param is not None:
            if group_param not in group_names:
                raise HTTPError(
                    status_code=404,
                    reason="the group name in the URL is not one you belong to"
                )
            form.group_name.choices = pairs([group_param])
            form.group_name.render_kw = {"readonly": "readonly"}
            form.group_name.data = group_param
        else:
            form.group_name.choices = pairs([""] + sorted(group_names))

        permission_param = self.get_argument("permission", None)
        if permission_param is not None:
            if permission_param not in permission_names:
                raise HTTPError(
                    status_code=404,
                    reason="an unrecognized permission is specified in the URL"
                )
            form.permission.choices = pairs([permission_param])
            form.permission.render_kw = {"readonly": "readonly"}
            form.permission.data = permission_param
        else:
            form.permission.choices = pairs(sorted(permission_names))

        argument_param = self.get_argument("argument", "")
        if argument_param:
            form.argument.render_kw = {"readonly": "readonly"}
            form.argument.data = argument_param

        return form, args_by_perm
Esempio n. 9
0
    def post(self, *args: Any, **kwargs: Any) -> None:
        name = self.get_path_argument("name")

        user = User.get(self.session, name=name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        form = UserShellForm(self.request.arguments)
        form.shell.choices = settings().shell
        if not form.validate():
            return self.render("user-shell.html",
                               form=form,
                               user=user,
                               alerts=self.get_form_alerts(form.errors))

        set_user_metadata(self.session, user.id, USER_METADATA_SHELL_KEY,
                          form.data["shell"])

        AuditLog.log(
            self.session,
            self.current_user.id,
            "changed_shell",
            "Changed shell: {}".format(form.data["shell"]),
            on_user_id=user.id,
        )

        return self.redirect("/users/{}?refresh=yes".format(user.name))
Esempio n. 10
0
 def baduser(self, message) -> None:
     self.set_status(403)
     self.raise_and_log_exception(HTTPError(403))
     how_to_get_help = settings().how_to_get_help
     self.render("errors/baduser.html",
                 message=message,
                 how_to_get_help=how_to_get_help)
Esempio n. 11
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        user_id = kwargs.get("user_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        user = User.get(self.session, user_id, name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        form = UserShellForm(self.request.arguments)
        form.shell.choices = settings().shell
        if not form.validate():
            return self.render(
                "user-shell.html", form=form, user=user, alerts=self.get_form_alerts(form.errors)
            )

        set_user_metadata(self.session, user.id, USER_METADATA_SHELL_KEY, form.data["shell"])

        AuditLog.log(
            self.session,
            self.current_user.id,
            "changed_shell",
            "Changed shell: {}".format(form.data["shell"]),
            on_user_id=user.id,
        )

        return self.redirect("/users/{}?refresh=yes".format(user.name))
Esempio n. 12
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        user_id = kwargs.get("user_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        user = User.get(self.session, user_id, name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        form = UserTokenForm(self.request.arguments)
        if not form.validate():
            return self.render(
                "user-token-add.html",
                form=form,
                user=user,
                alerts=self.get_form_alerts(form.errors),
            )

        try:
            token, secret = add_new_user_token(
                self.session, UserToken(name=form.data["name"], user=user))
            self.session.commit()
        except IntegrityError:
            self.session.rollback()
            form.name.errors.append("Name already in use.")
            return self.render(
                "user-token-add.html",
                form=form,
                user=user,
                alerts=self.get_form_alerts(form.errors),
            )

        AuditLog.log(
            self.session,
            self.current_user.id,
            "add_token",
            "Added token: {}".format(token.name),
            on_user_id=user.id,
        )

        email_context = {
            "actioner": self.current_user.name,
            "changed_user": user.name,
            "action": "added",
        }
        send_email(
            self.session,
            [user.name],
            "User token created",
            "user_tokens_changed",
            settings(),
            email_context,
        )
        return self.render("user-token-created.html",
                           token=token,
                           secret=secret)
Esempio n. 13
0
 def baduser(self, message):
     # type: (str) -> None
     self.set_status(403)
     self.raise_and_log_exception(tornado.web.HTTPError(403))
     how_to_get_help = settings().how_to_get_help
     self.render("errors/baduser.html",
                 message=message,
                 how_to_get_help=how_to_get_help)
Esempio n. 14
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        user_id = kwargs.get("user_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        user = User.get(self.session, user_id, name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        form = UserTokenForm(self.request.arguments)
        if not form.validate():
            return self.render(
                "user-token-add.html",
                form=form,
                user=user,
                alerts=self.get_form_alerts(form.errors),
            )

        try:
            token, secret = add_new_user_token(
                self.session, UserToken(name=form.data["name"], user=user)
            )
            self.session.commit()
        except IntegrityError:
            self.session.rollback()
            form.name.errors.append("Name already in use.")
            return self.render(
                "user-token-add.html",
                form=form,
                user=user,
                alerts=self.get_form_alerts(form.errors),
            )

        AuditLog.log(
            self.session,
            self.current_user.id,
            "add_token",
            "Added token: {}".format(token.name),
            on_user_id=user.id,
        )

        email_context = {
            "actioner": self.current_user.name,
            "changed_user": user.name,
            "action": "added",
        }
        send_email(
            self.session,
            [user.name],
            "User token created",
            "user_tokens_changed",
            settings(),
            email_context,
        )
        return self.render("user-token-created.html", token=token, secret=secret)
Esempio n. 15
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        user_id = kwargs.get("user_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        user = User.get(self.session, user_id, name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        form = UserPasswordForm(self.request.arguments)
        if not form.validate():
            return self.render(
                "user-password-add.html",
                form=form,
                user=user,
                alerts=self.get_form_alerts(form.errors),
            )

        pass_name = form.data["name"]
        password = form.data["password"]
        try:
            add_new_user_password(self.session, pass_name, password, user.id)
        except PasswordAlreadyExists:
            self.session.rollback()
            form.name.errors.append("Name already in use.")
            return self.render(
                "user-password-add.html",
                form=form,
                user=user,
                alerts=self.get_form_alerts(form.errors),
            )

        AuditLog.log(
            self.session,
            self.current_user.id,
            "add_password",
            "Added password: {}".format(pass_name),
            on_user_id=user.id,
        )

        email_context = {
            "actioner": self.current_user.name,
            "changed_user": user.name,
            "pass_name": pass_name,
        }
        send_email(
            self.session,
            [user.name],
            "User password created",
            "user_password_created",
            settings(),
            email_context,
        )
        return self.redirect("/users/{}?refresh=yes".format(user.name))
Esempio n. 16
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        user_id = kwargs.get("user_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        user = User.get(self.session, user_id, name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        form = UserPasswordForm(self.request.arguments)
        if not form.validate():
            return self.render(
                "user-password-add.html",
                form=form,
                user=user,
                alerts=self.get_form_alerts(form.errors),
            )

        pass_name = form.data["name"]
        password = form.data["password"]
        try:
            add_new_user_password(self.session, pass_name, password, user.id)
        except PasswordAlreadyExists:
            self.session.rollback()
            form.name.errors.append("Name already in use.")
            return self.render(
                "user-password-add.html",
                form=form,
                user=user,
                alerts=self.get_form_alerts(form.errors),
            )

        AuditLog.log(
            self.session,
            self.current_user.id,
            "add_password",
            "Added password: {}".format(pass_name),
            on_user_id=user.id,
        )

        email_context = {
            "actioner": self.current_user.name,
            "changed_user": user.name,
            "pass_name": pass_name,
        }
        send_email(
            self.session,
            [user.name],
            "User password created",
            "user_password_created",
            settings(),
            email_context,
        )
        return self.redirect("/users/{}?refresh=yes".format(user.name))
Esempio n. 17
0
    def get(self, *args: Any, **kwargs: Any) -> Iterator[Future]:
        # Check that the state parameter is correct.
        state = self.request.query_arguments.get("state", [b""])[-1]
        expected_state = self.get_cookie("github-link-state",
                                         "").encode("utf-8")
        self.clear_cookie("github-link-state")
        if not hmac.compare_digest(state, expected_state):
            self.badrequest()
            return

        code = self.get_query_argument("code")

        # Make sure we're modifying the authenticated user before doing more.
        user_id = kwargs["user_id"]
        user = User.get(self.session, user_id)
        if not user:
            self.notfound()
            return
        if self.current_user.id != user.id:
            self.forbidden()
            return

        github_client = GitHubClient(_get_github_http_client(),
                                     settings().http_proxy_host,
                                     settings().http_proxy_port)
        oauth_token = yield github_client.get_oauth_access_token(
            settings().github_app_client_id,
            get_plugin_proxy().get_github_app_client_secret(),
            code,
            state,
        )
        gh_username = yield github_client.get_username(oauth_token)

        AuditLog.log(
            self.session,
            self.current_user.id,
            "changed_github_username",
            "Changed GitHub username: {}".format(gh_username),
            on_user_id=user.id,
        )
        set_user_metadata(self.session, user.id,
                          USER_METADATA_GITHUB_USERNAME_KEY, gh_username)

        self.redirect("/users/{}?refresh=yes".format(user.name))
Esempio n. 18
0
    def _build_form(self, data):
        # type: (Optional[int]) -> Tuple[PermissionRequestForm, Dict[Permission, List[str]]]
        """Build the permission request form given the request and POST data.

        Normally all fields of the form will be editable.  But if the URL
        locks down a specific value for the group, permission, or argument,
        then the specified fields will display those values and will be
        grayed out and not editable.

        """
        session = self.session
        current_user = self.current_user

        def pairs(seq):
            # type: (Iterable[str]) -> List[Tuple[str, str]]
            return [(item, item) for item in seq]

        form = PermissionRequestForm(data)

        group_names = {g.groupname for g, e in get_groups_by_user(session, current_user)}
        args_by_perm = get_grantable_permissions(
            session, settings().restricted_ownership_permissions
        )
        permission_names = {p for p in args_by_perm}

        group_param = self.get_argument("group", None)
        if group_param is not None:
            if group_param not in group_names:
                raise HTTPError(
                    status_code=404, reason="the group name in the URL is not one you belong to"
                )
            form.group_name.choices = pairs([group_param])
            form.group_name.render_kw = {"readonly": "readonly"}
        else:
            form.group_name.choices = pairs([""] + sorted(group_names))

        permission_param = self.get_argument("permission", None)
        if permission_param is not None:
            if permission_param not in permission_names:
                raise HTTPError(
                    status_code=404, reason="an unrecognized permission is specified in the URL"
                )
            form.permission_name.choices = pairs([permission_param])
            form.permission_name.render_kw = {"readonly": "readonly"}
        else:
            form.permission_name.choices = pairs([""] + sorted(permission_names))

        argument_param = self.get_argument("argument", "")
        if argument_param:
            form.argument.render_kw = {"readonly": "readonly"}
            form.argument.data = argument_param

        return form, args_by_perm
Esempio n. 19
0
    def get(self, *args: Any, **kwargs: Any) -> None:
        name = self.get_path_argument("name")

        user = User.get(self.session, name=name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        form = UserShellForm()
        form.shell.choices = settings().shell

        self.render("user-shell.html", form=form, user=user)
Esempio n. 20
0
    def prepare(self) -> None:
        username = self.request.headers.get(settings().user_auth_header)

        try:
            user = self.get_or_create_user(username)
        except InvalidUser as e:
            self.baduser(str(e))
            self.finish()
            return

        if user and user.enabled:
            self.current_user = user
        else:
            self.baduser("{} is not an active account".format(username))
            self.finish()
Esempio n. 21
0
    def prepare(self):
        # type: () -> None
        username = self.request.headers.get(settings().user_auth_header)

        try:
            user = self.get_or_create_user(username)
        except InvalidUser as e:
            self.baduser(str(e))
            self.finish()
            return

        if user and user.enabled:
            self.current_user = user
        else:
            self.baduser("{} is not an active account".format(username))
            self.finish()
Esempio n. 22
0
    def get(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        user_id = kwargs.get("user_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        user = User.get(self.session, user_id, name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        form = UserShellForm()
        form.shell.choices = settings().shell

        self.render("user-shell.html", form=form, user=user)
Esempio n. 23
0
    def get(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        user_id = kwargs.get("user_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        user = User.get(self.session, user_id, name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        form = UserShellForm()
        form.shell.choices = settings().shell

        self.render("user-shell.html", form=form, user=user)
Esempio n. 24
0
    def initialize(self, *args: Any, **kwargs: Any) -> None:
        self.graph = Graph()
        self.session = self.settings["session"]()  # type: Session
        self.template_engine = self.settings[
            "template_engine"]  # type: FrontendTemplateEngine
        self.plugins = get_plugin_proxy()
        session_factory = SingletonSessionFactory(self.session)
        self.usecase_factory = create_graph_usecase_factory(
            settings(), self.plugins, session_factory)

        if self.get_argument("_profile", False):
            self.perf_collector = Collector()
            self.perf_trace_uuid = str(uuid4())  # type: Optional[str]
            self.perf_collector.start()
        else:
            self.perf_collector = None
            self.perf_trace_uuid = None

        self._request_start_time = datetime.utcnow()
Esempio n. 25
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        user_id = kwargs.get("user_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]
        key_id = kwargs["key_id"]  # type: int

        user = User.get(self.session, user_id, name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        try:
            key = get_public_key(self.session, user.id, key_id)
            delete_public_key(self.session, user.id, key_id)
        except KeyNotFound:
            return self.notfound()

        AuditLog.log(
            self.session,
            self.current_user.id,
            "delete_public_key",
            "Deleted public key: {}".format(key.fingerprint_sha256),
            on_user_id=user.id,
        )

        email_context = {
            "actioner": self.current_user.name,
            "changed_user": user.name,
            "action": "removed",
        }
        send_email(
            self.session,
            [user.name],
            "Public SSH key removed",
            "ssh_keys_changed",
            settings(),
            email_context,
        )

        return self.redirect("/users/{}?refresh=yes".format(user.name))
Esempio n. 26
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        user_id = kwargs.get("user_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]
        key_id = kwargs["key_id"]  # type: int

        user = User.get(self.session, user_id, name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        try:
            key = get_public_key(self.session, user.id, key_id)
            delete_public_key(self.session, user.id, key_id)
        except KeyNotFound:
            return self.notfound()

        AuditLog.log(
            self.session,
            self.current_user.id,
            "delete_public_key",
            "Deleted public key: {}".format(key.fingerprint_sha256),
            on_user_id=user.id,
        )

        email_context = {
            "actioner": self.current_user.name,
            "changed_user": user.name,
            "action": "removed",
        }
        send_email(
            self.session,
            [user.name],
            "Public SSH key removed",
            "ssh_keys_changed",
            settings(),
            email_context,
        )

        return self.redirect("/users/{}?refresh=yes".format(user.name))
Esempio n. 27
0
    def get_or_create_user(self, username):
        # type: (str) -> Optional[User]
        """Retrieve or create the User object for the authenticated user.

        This is done in a separate method called by prepare instead of in the magic Tornado
        get_current_user method because exceptions thrown by the latter are caught by Tornado and
        not propagated to the caller, and we want to use exceptions to handle invalid users and
        then return an error page in prepare.
        """
        if not username:
            return None

        # Users must be fully qualified
        if not re.match("^{}$".format(USERNAME_VALIDATION), username):
            raise InvalidUser("{} does not match {}".format(
                username, USERNAME_VALIDATION))

        # User must exist in the database and be active
        try:
            user, created = User.get_or_create(self.session, username=username)
            if created:
                logging.info("Created new user %s", username)
                self.session.commit()
                # Because the graph doesn't initialize until the updates table
                # is populated, we need to refresh the graph here in case this
                # is the first update.
                self.graph.update_from_db(self.session)
        except sqlalchemy.exc.OperationalError:
            # Failed to connect to database or create user, try to reconfigure the db. This invokes
            # the fetcher to try to see if our URL string has changed.
            Session.configure(bind=get_db_engine(settings().database))
            raise DatabaseFailure()

        # service accounts are, by definition, not interactive users
        if user.is_service_account:
            raise InvalidUser("{} is a service account".format(username))

        return user
Esempio n. 28
0
    def initialize(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        self.graph = Graph()
        self.session = self.settings["session"]()  # type: Session
        self.template_engine = self.settings["template_engine"]  # type: FrontendTemplateEngine
        self.plugins = get_plugin_proxy()
        session_factory = SingletonSessionFactory(self.session)
        self.usecase_factory = create_graph_usecase_factory(
            settings(), self.plugins, session_factory
        )

        if self.get_argument("_profile", False):
            self.perf_collector = Collector()
            self.perf_trace_uuid = str(uuid4())  # type: Optional[str]
            self.perf_collector.start()
        else:
            self.perf_collector = None
            self.perf_trace_uuid = None

        self._request_start_time = datetime.utcnow()

        stats.log_rate("requests", 1)
        stats.log_rate("requests_{}".format(self.__class__.__name__), 1)
Esempio n. 29
0
    def get_or_create_user(self, username):
        # type: (str) -> Optional[User]
        """Retrieve or create the User object for the authenticated user.

        This is done in a separate method called by prepare instead of in the magic Tornado
        get_current_user method because exceptions thrown by the latter are caught by Tornado and
        not propagated to the caller, and we want to use exceptions to handle invalid users and
        then return an error page in prepare.
        """
        if not username:
            return None

        # Users must be fully qualified
        if not re.match("^{}$".format(USERNAME_VALIDATION), username):
            raise InvalidUser("{} does not match {}".format(username, USERNAME_VALIDATION))

        # User must exist in the database and be active
        try:
            user, created = User.get_or_create(self.session, username=username)
            if created:
                logging.info("Created new user %s", username)
                self.session.commit()
                # Because the graph doesn't initialize until the updates table
                # is populated, we need to refresh the graph here in case this
                # is the first update.
                self.graph.update_from_db(self.session)
        except sqlalchemy.exc.OperationalError:
            # Failed to connect to database or create user, try to reconfigure the db. This invokes
            # the fetcher to try to see if our URL string has changed.
            Session.configure(bind=get_db_engine(settings().database))
            raise DatabaseFailure()

        # service accounts are, by definition, not interactive users
        if user.is_service_account:
            raise InvalidUser("{} is a service account".format(username))

        return user
Esempio n. 30
0
    def initialize(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        self.graph = Graph()
        self.session = self.settings["session"]()  # type: Session
        self.template_engine = self.settings[
            "template_engine"]  # type: FrontendTemplateEngine
        self.plugins = get_plugin_proxy()
        session_factory = SingletonSessionFactory(self.session)
        self.usecase_factory = create_graph_usecase_factory(
            settings(), self.plugins, session_factory)

        if self.get_argument("_profile", False):
            self.perf_collector = Collector()
            self.perf_trace_uuid = str(uuid4())  # type: Optional[str]
            self.perf_collector.start()
        else:
            self.perf_collector = None
            self.perf_trace_uuid = None

        self._request_start_time = datetime.utcnow()

        stats.log_rate("requests", 1)
        stats.log_rate("requests_{}".format(self.__class__.__name__), 1)
        logging.error("initialized")
Esempio n. 31
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        form = AuditCreateForm(self.request.arguments)
        if not form.validate():
            return self.render(
                "audit-create.html", form=form, alerts=self.get_form_alerts(form.errors)
            )

        if not user_has_permission(self.session, self.current_user, AUDIT_MANAGER):
            return self.forbidden()

        # Need to lock this and prevent someone from requesting another set of audits
        # while this is processing
        lock = Lock()

        # Step 1, detect if there are non-completed audits and fail if so.
        with lock:
            open_audits = self.session.query(Audit).filter(Audit.complete == False).all()
            if open_audits:
                raise Exception("Sorry, there are audits in progress.")
            ends_at = datetime.strptime(form.data["ends_at"], "%m/%d/%Y")

            # Step 2, find all audited groups and schedule audits for each.
            audited_groups = []
            for groupname in self.graph.groups:
                if not self.graph.get_group_details(groupname)["audited"]:
                    continue
                group = Group.get(self.session, name=groupname)
                assert group, f"Graph contains nonexistent group {groupname}"
                audit = Audit(group_id=group.id, ends_at=ends_at)
                try:
                    audit.add(self.session)
                    self.session.flush()
                except IntegrityError:
                    self.session.rollback()
                    raise Exception("Failed to start the audit. Please try again.")

                # Update group with new audit
                audited_groups.append(group)
                group.audit_id = audit.id

                # Step 3, now get all members of this group and set up audit rows for those edges.
                for member in group.my_members().values():
                    auditmember = AuditMember(audit_id=audit.id, edge_id=member.edge_id)
                    try:
                        auditmember.add(self.session)
                    except IntegrityError:
                        self.session.rollback()
                        raise Exception("Failed to start the audit. Please try again.")

            self.session.commit()

        AuditLog.log(
            self.session,
            self.current_user.id,
            "start_audit",
            "Started global audit.",
            category=AuditLogCategory.audit,
        )

        # Calculate schedule of emails, basically we send emails at various periods in advance
        # of the end of the audit period.
        schedule_times = []
        not_before = datetime.utcnow() + timedelta(1)
        for days_prior in (28, 21, 14, 7, 3, 1):
            email_time = ends_at - timedelta(days_prior)
            email_time.replace(hour=17, minute=0, second=0)
            if email_time > not_before:
                schedule_times.append((days_prior, email_time))

        # Now send some emails. We do this separately/later to ensure that the audits are all
        # created. Email notifications are sent multiple times if group audits are still
        # outstanding.
        for group in audited_groups:
            mail_to = [
                member.name
                for member in group.my_users()
                if GROUP_EDGE_ROLES[member.role] in ("owner", "np-owner")
            ]

            send_email(
                self.session,
                mail_to,
                "Group Audit: {}".format(group.name),
                "audit_notice",
                settings(),
                {"group": group.name, "ends_at": ends_at},
            )

            for days_prior, email_time in schedule_times:
                send_async_email(
                    self.session,
                    mail_to,
                    "Group Audit: {} - {} day(s) left".format(group.name, days_prior),
                    "audit_notice_reminder",
                    settings(),
                    {"group": group.name, "ends_at": ends_at, "days_left": days_prior},
                    email_time,
                    async_key="audit-{}".format(group.id),
                )

        return self.redirect("/audits")
Esempio n. 32
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        group_id = kwargs.get("group_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        group = Group.get(self.session, group_id, name)
        if not group or not group.enabled:
            return self.notfound()

        members = group.my_members()
        member_groups = {g for t, g in members if t == "Group"}
        user_is_member = self._is_user_a_member(group, members)

        form = GroupJoinForm(self.request.arguments)
        form.member.choices = self._get_choices(group, member_groups,
                                                user_is_member)
        if not form.validate():
            return self.render("group-join.html",
                               form=form,
                               group=group,
                               alerts=self.get_form_alerts(form.errors))

        member = self._get_member(form.data["member"])
        if not member:
            return self.render(
                "group-join.html",
                form=form,
                group=group,
                alerts=[
                    Alert(
                        "danger", "Unknown user or group: {}".format(
                            form.data["member"]))
                ],
            )

        fail_message = "This join is denied with this role at this time."
        try:
            user_can_join = assert_can_join(group,
                                            member,
                                            role=form.data["role"])
        except UserNotAuditor as e:
            user_can_join = False
            fail_message = str(e)
        if not user_can_join:
            return self.render(
                "group-join.html",
                form=form,
                group=group,
                alerts=[
                    Alert("danger", fail_message, "Audit Policy Enforcement")
                ],
            )

        if group.canjoin == "nobody":
            fail_message = "This group cannot be joined at this time."
            return self.render("group-join.html",
                               form=form,
                               group=group,
                               alerts=[Alert("danger", fail_message)])

        if group.require_clickthru_tojoin:
            if not form.data["clickthru_agreement"]:
                return self.render(
                    "group-join.html",
                    form=form,
                    group=group,
                    alerts=[
                        Alert(
                            "danger",
                            "please accept review of the group's description",
                            "Clickthru Enforcement",
                        )
                    ],
                )

        # We only use the default expiration time if no expiration time was given
        # This does mean that if a user wishes to join a group with no expiration
        # (even with an owner's permission) that has an auto expiration, they must
        # first be accepted to the group and then have the owner edit the user to
        # have no expiration.

        expiration = None
        if form.data["expiration"]:
            expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y")
        elif group.auto_expire:
            expiration = datetime.utcnow() + group.auto_expire

        request = group.add_member(
            requester=self.current_user,
            user_or_group=member,
            reason=form.data["reason"],
            status=GROUP_JOIN_CHOICES[group.canjoin],
            expiration=expiration,
            role=form.data["role"],
        )
        self.session.commit()

        if group.canjoin == "canask":
            AuditLog.log(
                self.session,
                self.current_user.id,
                "join_group",
                "{} requested to join with role: {}".format(
                    member.name, form.data["role"]),
                on_group_id=group.id,
            )

            mail_to = [
                user.name for user in group.my_users()
                if GROUP_EDGE_ROLES[user.role] in ("manager", "owner",
                                                   "np-owner")
            ]

            email_context = {
                "requester": member.name,
                "requested_by": self.current_user.name,
                "request_id": request.id,
                "group_name": group.name,
                "reason": form.data["reason"],
                "expiration": expiration,
                "role": form.data["role"],
                "references_header": request.reference_id,
            }

            subj = self.render_template("email/pending_request_subj.tmpl",
                                        group=group.name,
                                        user=self.current_user.name)
            send_email(self.session, mail_to, subj, "pending_request",
                       settings(), email_context)

        elif group.canjoin == "canjoin":
            AuditLog.log(
                self.session,
                self.current_user.id,
                "join_group",
                "{} auto-approved to join with role: {}".format(
                    member.name, form.data["role"]),
                on_group_id=group.id,
            )
        else:
            raise Exception("Need to update the GroupJoin.post audit logging")

        return self.redirect("/groups/{}?refresh=yes".format(group.name))
Esempio n. 33
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        group_id = kwargs.get("group_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        group = Group.get(self.session, group_id, name)
        if not group:
            return self.notfound()

        # Only members can request permissions
        if not self.current_user.is_member(group.my_members()):
            return self.forbidden()

        # check inputs
        args_by_perm = get_grantable_permissions(
            self.session, settings().restricted_ownership_permissions
        )
        dropdown_form, text_form = GroupPermissionRequest._get_forms(
            args_by_perm, self.request.arguments
        )

        argument_type = self.request.arguments.get("argument_type")
        if argument_type and argument_type[0].decode() == "text":
            form = text_form
        elif argument_type and argument_type[0].decode() == "dropdown":
            form = dropdown_form
            form.argument.choices = [(a, a) for a in args_by_perm[form.permission_name.data]]
        else:
            # someone messing with the form
            self.log_message(
                "unknown argument type", group_name=group.name, argument_type=argument_type
            )
            return self.forbidden()

        if not form.validate():
            return self.render(
                "group-permission-request.html",
                dropdown_form=dropdown_form,
                text_form=text_form,
                group=group,
                args_by_perm_json=json.dumps(args_by_perm),
                alerts=self.get_form_alerts(form.errors),
                dropdown_help=settings().permission_request_dropdown_help,
                text_help=settings().permission_request_text_help,
            )

        permission = get_permission(self.session, form.permission_name.data)
        assert permission is not None, "our prefilled permission should exist or we have problems"

        # save off request
        try:
            request = permissions.create_request(
                self.session,
                self.current_user,
                group,
                permission,
                form.argument.data,
                form.reason.data,
            )
        except permissions.RequestAlreadyGranted:
            alerts = [Alert("danger", "This group already has this permission and argument.")]
        except permissions.RequestAlreadyExists:
            alerts = [
                Alert(
                    "danger",
                    "Request for permission and argument already exists, please wait patiently.",
                )
            ]
        except permissions.NoOwnersAvailable:
            self.log_message(
                "prefilled perm+arg have no owner",
                group_name=group.name,
                permission_name=permission.name,
                argument=form.argument.data,
            )
            alerts = [
                Alert(
                    "danger",
                    "No owners available for requested permission and argument."
                    " If this error persists please contact an adminstrator.",
                )
            ]
        except UserNotAuditor as e:
            alerts = [Alert("danger", str(e))]
        else:
            alerts = []

        if alerts:
            return self.render(
                "group-permission-request.html",
                dropdown_form=dropdown_form,
                text_form=text_form,
                group=group,
                args_by_perm_json=json.dumps(args_by_perm),
                alerts=alerts,
            )
        else:
            return self.redirect("/permissions/requests/{}".format(request.id))
Esempio n. 34
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        group_id = kwargs.get("group_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        group = Group.get(self.session, group_id, name)
        if not group:
            return self.notfound()

        form = GroupJoinForm(self.request.arguments)
        form.member.choices = self._get_choices(group)
        if not form.validate():
            return self.render(
                "group-join.html", form=form, group=group, alerts=self.get_form_alerts(form.errors)
            )

        member = self._get_member(form.data["member"])
        if not member:
            return self.render(
                "group-join.html",
                form=form,
                group=group,
                alerts=[Alert("danger", "Unknown user or group: {}".format(form.data["member"]))],
            )

        fail_message = "This join is denied with this role at this time."
        try:
            user_can_join = assert_can_join(group, member, role=form.data["role"])
        except UserNotAuditor as e:
            user_can_join = False
            fail_message = str(e)
        if not user_can_join:
            return self.render(
                "group-join.html",
                form=form,
                group=group,
                alerts=[Alert("danger", fail_message, "Audit Policy Enforcement")],
            )

        if group.canjoin == "nobody":
            fail_message = "This group cannot be joined at this time."
            return self.render(
                "group-join.html", form=form, group=group, alerts=[Alert("danger", fail_message)]
            )

        if group.require_clickthru_tojoin:
            if not form.data["clickthru_agreement"]:
                return self.render(
                    "group-join.html",
                    form=form,
                    group=group,
                    alerts=[
                        Alert(
                            "danger",
                            "please accept review of the group's description",
                            "Clickthru Enforcement",
                        )
                    ],
                )

        # We only use the default expiration time if no expiration time was given
        # This does mean that if a user wishes to join a group with no expiration
        # (even with an owner's permission) that has an auto expiration, they must
        # first be accepted to the group and then have the owner edit the user to
        # have no expiration.

        expiration = None
        if form.data["expiration"]:
            expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y")
        elif group.auto_expire:
            expiration = datetime.utcnow() + group.auto_expire

        request = group.add_member(
            requester=self.current_user,
            user_or_group=member,
            reason=form.data["reason"],
            status=GROUP_JOIN_CHOICES[group.canjoin],
            expiration=expiration,
            role=form.data["role"],
        )
        self.session.commit()

        if group.canjoin == "canask":
            AuditLog.log(
                self.session,
                self.current_user.id,
                "join_group",
                "{} requested to join with role: {}".format(member.name, form.data["role"]),
                on_group_id=group.id,
            )

            mail_to = [
                user.name
                for user in group.my_users()
                if GROUP_EDGE_ROLES[user.role] in ("manager", "owner", "np-owner")
            ]

            email_context = {
                "requester": member.name,
                "requested_by": self.current_user.name,
                "request_id": request.id,
                "group_name": group.name,
                "reason": form.data["reason"],
                "expiration": expiration,
                "role": form.data["role"],
                "references_header": request.reference_id,
            }

            subj = self.render_template(
                "email/pending_request_subj.tmpl", group=group.name, user=self.current_user.name
            )
            send_email(self.session, mail_to, subj, "pending_request", settings(), email_context)

        elif group.canjoin == "canjoin":
            AuditLog.log(
                self.session,
                self.current_user.id,
                "join_group",
                "{} auto-approved to join with role: {}".format(member.name, form.data["role"]),
                on_group_id=group.id,
            )
        else:
            raise Exception("Need to update the GroupJoin.post audit logging")

        return self.redirect("/groups/{}?refresh=yes".format(group.name))
Esempio n. 35
0
def test_github(session, users, http_client, base_url, mocker):  # noqa: F811
    user = users["*****@*****.**"]
    assert get_user_metadata_by_key(session, user.id,
                                    USER_METADATA_GITHUB_USERNAME_KEY) is None

    user = User.get(session, name=user.username)
    fe_url = url(base_url, "/github/link_begin/{}".format(user.id))
    mocker.patch.object(settings(), "github_app_client_id", "a-client-id")
    resp = yield http_client.fetch(
        fe_url,
        method="GET",
        headers={"X-Grouper-User": user.username},
        follow_redirects=False,
        raise_error=False,
    )
    assert resp.code == 302
    redir_url = urlparse(resp.headers["Location"])
    assert redir_url.netloc == "github.com"
    assert redir_url.path == "/login/oauth/authorize"
    query_params = parse_qs(redir_url.query)
    assert query_params["client_id"] == ["a-client-id"]
    (state, ) = query_params["state"]
    assert "github-link-state={}".format(state) in resp.headers["Set-cookie"]
    assert query_params["redirect_uri"] == [
        "http://127.0.0.1:8888/github/link_complete/{}".format(user.id)
    ]

    fe_url = url(
        base_url, "/github/link_complete/{}?code=tempcode&state={}".format(
            user.id, state))
    with pytest.raises(HTTPError) as excinfo:
        yield http_client.fetch(
            fe_url,
            method="GET",
            headers={
                "X-Grouper-User": user.username,
                "Cookie": "github-link-state=bogus-state"
            },
        )
    assert excinfo.value.code == 400

    recorder = FakeGitHubHttpClient()
    proxy_plugin = PluginProxy([SecretPlugin()])
    mocker.patch("grouper.fe.handlers.github._get_github_http_client",
                 lambda: recorder)
    mocker.patch("grouper.fe.handlers.github.get_plugin_proxy",
                 lambda: proxy_plugin)
    mocker.patch.object(settings(), "http_proxy_host", "proxy-server")
    mocker.patch.object(settings(), "http_proxy_port", 42)
    resp = yield http_client.fetch(
        fe_url,
        method="GET",
        headers={
            "X-Grouper-User": user.username,
            "Cookie": "github-link-state=" + state
        },
    )
    authorize_request, user_request = recorder.requests
    assert authorize_request.proxy_host == "proxy-server"
    assert authorize_request.proxy_port == 42
    assert user_request.proxy_host == "proxy-server"
    assert user_request.proxy_port == 42
    authorize_params = parse_qs(authorize_request.body)
    assert authorize_params[b"code"] == [b"tempcode"]
    assert authorize_params[b"state"] == [state.encode("ascii")]
    assert authorize_params[b"client_id"] == [b"a-client-id"]
    assert authorize_params[b"client_secret"] == [b"client-secret"]
    assert user_request.headers["Authorization"] == "token a-access-token"
    assert (get_user_metadata_by_key(session, user.id,
                                     USER_METADATA_GITHUB_USERNAME_KEY)
            is not None)
    assert (get_user_metadata_by_key(
        session, user.id,
        USER_METADATA_GITHUB_USERNAME_KEY).data_value == "zorkian-on-gh")

    audit_entries = AuditLog.get_entries(session,
                                         on_user_id=user.id,
                                         action="changed_github_username")
    assert len(audit_entries) == 1
    assert audit_entries[
        0].description == "Changed GitHub username: zorkian-on-gh"

    fe_url = url(base_url, "/users/{}/github/clear".format(user.username))
    resp = yield http_client.fetch(fe_url,
                                   method="POST",
                                   headers={"X-Grouper-User": user.username},
                                   body=b"")
    assert resp.code == 200
    assert get_user_metadata_by_key(session, user.id,
                                    USER_METADATA_GITHUB_USERNAME_KEY) is None

    audit_entries = AuditLog.get_entries(session,
                                         on_user_id=user.id,
                                         action="changed_github_username")
    assert len(audit_entries) == 2
    audit_entries.sort(key=operator.attrgetter("id"))
    assert audit_entries[1].description == "Cleared GitHub link"
Esempio n. 36
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        group_id = kwargs.get("group_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        group = Group.get(self.session, group_id, name)
        if not group:
            return self.notfound()

        if not user_can_manage_group(self.session, group, self.current_user):
            return self.forbidden()

        members = group.my_members()
        my_role = user_role(self.current_user, members)
        form = self.get_form(role=my_role)
        if not form.validate():
            return self.render("group-add.html",
                               form=form,
                               group=group,
                               alerts=self.get_form_alerts(form.errors))

        member = get_user_or_group(self.session, form.data["member"])
        if member.type == "User" and is_role_user(self.session, member):
            # For service accounts, we want to always add the group to other groups, not the user
            member = get_role_user(self.session, user=member).group
        if not member:
            form.member.errors.append("User or group not found.")
        elif (member.type, member.name) in group.my_members():
            form.member.errors.append(
                "User or group is already a member of this group.")
        elif group.name == member.name:
            form.member.errors.append(
                "By definition, this group is a member of itself already.")

        # Ensure this doesn't violate auditing constraints
        fail_message = "This join is denied with this role at this time."
        try:
            user_can_join = assert_can_join(group,
                                            member,
                                            role=form.data["role"])
        except UserNotAuditor as e:
            user_can_join = False
            fail_message = str(e)
        if not user_can_join:
            form.member.errors.append(fail_message)

        if form.member.errors:
            return self.render("group-add.html",
                               form=form,
                               group=group,
                               alerts=self.get_form_alerts(form.errors))

        expiration = None
        if form.data["expiration"]:
            expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y")

        try:
            group.add_member(
                requester=self.current_user,
                user_or_group=member,
                reason=form.data["reason"],
                status="actioned",
                expiration=expiration,
                role=form.data["role"],
            )
        except InvalidRoleForMember as e:
            return self.render("group-add.html",
                               form=form,
                               group=group,
                               alerts=[Alert("danger", str(e))])

        self.session.commit()

        on_user_id = member.id if member.type == "User" else None
        AuditLog.log(
            self.session,
            self.current_user.id,
            "join_group",
            "{} added to group with role: {}".format(member.name,
                                                     form.data["role"]),
            on_group_id=group.id,
            on_user_id=on_user_id,
        )

        if member.type == "User":
            send_email(
                self.session,
                [member.name],
                "Added to group: {}".format(group.name),
                "request_actioned",
                settings(),
                {
                    "group_name": group.name,
                    "actioned_by": self.current_user.name,
                    "reason": form.data["reason"],
                    "expiration": expiration,
                    "role": form.data["role"],
                },
            )

        return self.redirect("/groups/{}?refresh=yes".format(group.name))
Esempio n. 37
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        group_id = kwargs.get("group_id")  # type: Optional[str]
        name = kwargs.get("name")  # type: Optional[str]

        group = Group.get(self.session, group_id, name)
        if not group:
            return self.notfound()

        if "@" not in self.request.arguments["name"][0].decode():
            self.request.arguments["name"][0] += (
                "@" + settings().service_account_email_domain).encode()

        if not can_create_service_account(self.session, self.current_user,
                                          group):
            return self.forbidden()

        form = ServiceAccountCreateForm(self.request.arguments)
        if not form.validate():
            return self.render(
                "service-account-create.html",
                form=form,
                group=group,
                alerts=self.get_form_alerts(form.errors),
            )

        if form.data["name"].split(
                "@")[-1] != settings().service_account_email_domain:
            form.name.errors.append(
                "All service accounts must have a username ending in {}".
                format(settings().service_account_email_domain))
            return self.render(
                "service-account-create.html",
                form=form,
                group=group,
                alerts=self.get_form_alerts(form.errors),
            )

        try:
            create_service_account(
                self.session,
                self.current_user,
                form.data["name"],
                form.data["description"],
                form.data["machine_set"],
                group,
            )
        except DuplicateServiceAccount:
            form.name.errors.append(
                "A user with name {} already exists".format(form.data["name"]))
        except BadMachineSet as e:
            form.machine_set.errors.append(str(e))

        if form.name.errors or form.machine_set.errors:
            return self.render(
                "service-account-create.html",
                form=form,
                group=group,
                alerts=self.get_form_alerts(form.errors),
            )

        url = "/groups/{}/service/{}?refresh=yes".format(
            group.name, form.data["name"])
        return self.redirect(url)
Esempio n. 38
0
    def post(self, *args: Any, **kwargs: Any) -> None:
        name = self.get_path_argument("name")

        group = Group.get(self.session, name=name)
        if not group or not group.enabled:
            return self.notfound()

        members = group.my_members()
        member_groups = {g for t, g in members if t == "Group"}
        user_is_member = self._is_user_a_member(group, members)

        form = GroupJoinForm(self.request.arguments)
        form.member.choices = self._get_choices(group, member_groups,
                                                user_is_member)
        if not form.validate():
            return self.render("group-join.html",
                               form=form,
                               group=group,
                               alerts=self.get_form_alerts(form.errors))

        member = self._get_member(form.data["member"])
        if not member:
            return self.render(
                "group-join.html",
                form=form,
                group=group,
                alerts=[
                    Alert(
                        "danger", "Unknown user or group: {}".format(
                            form.data["member"]))
                ],
            )

        try:
            assert_can_join(group, member, role=form.data["role"])
        except UserNotAuditor as e:
            return self.render(
                "group-join.html",
                form=form,
                group=group,
                alerts=[Alert("danger", str(e), "Audit Policy Enforcement")],
            )

        if group.canjoin == "nobody":
            fail_message = "This group cannot be joined at this time."
            return self.render("group-join.html",
                               form=form,
                               group=group,
                               alerts=[Alert("danger", fail_message)])

        if group.require_clickthru_tojoin:
            if not form.data["clickthru_agreement"]:
                return self.render(
                    "group-join.html",
                    form=form,
                    group=group,
                    alerts=[
                        Alert(
                            "danger",
                            "please accept review of the group's description",
                            "Clickthru Enforcement",
                        )
                    ],
                )

        # We only use the default expiration time if no expiration time was given
        # This does mean that if a user wishes to join a group with no expiration
        # (even with an owner's permission) that has an auto expiration, they must
        # first be accepted to the group and then have the owner edit the user to
        # have no expiration.

        expiration = None
        if form.data["expiration"]:
            expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y")
        elif group.auto_expire:
            expiration = datetime.utcnow() + group.auto_expire

        # If the requested role is member, set the status based on the group's canjoin setting,
        # which automatically actions the request if the group can be joined by anyone and
        # otherwise sets it pending.
        #
        # However, we don't want to let people autojoin as owner or np-owner even to otherwise open
        # groups, so if the role is not member, force the status to pending.
        if form.data["role"] == "member":
            status = GROUP_JOIN_CHOICES[group.canjoin]
        else:
            status = "pending"

        try:
            request = group.add_member(
                requester=self.current_user,
                user_or_group=member,
                reason=form.data["reason"],
                status=status,
                expiration=expiration,
                role=form.data["role"],
            )
        except InvalidRoleForMember as e:
            return self.render(
                "group-join.html",
                form=form,
                group=group,
                alerts=[Alert("danger", str(e), "Invalid Role")],
            )
        self.session.commit()

        if status == "pending":
            AuditLog.log(
                self.session,
                self.current_user.id,
                "join_group",
                "{} requested to join with role: {}".format(
                    member.name, form.data["role"]),
                on_group_id=group.id,
            )

            mail_to = [
                user.name for user in group.my_users()
                if GROUP_EDGE_ROLES[user.role] in ("manager", "owner",
                                                   "np-owner")
            ]

            email_context = {
                "requester": member.name,
                "requested_by": self.current_user.name,
                "request_id": request.id,
                "group_name": group.name,
                "reason": form.data["reason"],
                "expiration": expiration,
                "role": form.data["role"],
                "references_header": request.reference_id,
            }

            subj = self.render_template("email/pending_request_subj.tmpl",
                                        group=group.name,
                                        user=self.current_user.name)
            send_email(self.session, mail_to, subj, "pending_request",
                       settings(), email_context)

        elif status == "actioned":
            AuditLog.log(
                self.session,
                self.current_user.id,
                "join_group",
                "{} auto-approved to join with role: {}".format(
                    member.name, form.data["role"]),
                on_group_id=group.id,
            )
        else:
            raise Exception(f"Unknown join status {status}")

        return self.redirect("/groups/{}?refresh=yes".format(group.name))
Esempio n. 39
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        group_id = kwargs.get("group_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        group = Group.get(self.session, group_id, name)
        if not group:
            return self.notfound()

        if not user_can_manage_group(self.session, group, self.current_user):
            return self.forbidden()

        members = group.my_members()
        my_role = user_role(self.current_user, members)
        form = self.get_form(role=my_role)
        if not form.validate():
            return self.render(
                "group-add.html", form=form, group=group, alerts=self.get_form_alerts(form.errors)
            )

        member = get_user_or_group(self.session, form.data["member"])
        if member.type == "User" and is_role_user(self.session, member):
            # For service accounts, we want to always add the group to other groups, not the user
            member = get_role_user(self.session, user=member).group
        if not member:
            form.member.errors.append("User or group not found.")
        elif (member.type, member.name) in group.my_members():
            form.member.errors.append("User or group is already a member of this group.")
        elif group.name == member.name:
            form.member.errors.append("By definition, this group is a member of itself already.")

        # Ensure this doesn't violate auditing constraints
        fail_message = "This join is denied with this role at this time."
        try:
            user_can_join = assert_can_join(group, member, role=form.data["role"])
        except UserNotAuditor as e:
            user_can_join = False
            fail_message = str(e)
        if not user_can_join:
            form.member.errors.append(fail_message)

        if form.member.errors:
            return self.render(
                "group-add.html", form=form, group=group, alerts=self.get_form_alerts(form.errors)
            )

        expiration = None
        if form.data["expiration"]:
            expiration = datetime.strptime(form.data["expiration"], "%m/%d/%Y")

        try:
            group.add_member(
                requester=self.current_user,
                user_or_group=member,
                reason=form.data["reason"],
                status="actioned",
                expiration=expiration,
                role=form.data["role"],
            )
        except InvalidRoleForMember as e:
            return self.render(
                "group-add.html", form=form, group=group, alerts=[Alert("danger", str(e))]
            )

        self.session.commit()

        on_user_id = member.id if member.type == "User" else None
        AuditLog.log(
            self.session,
            self.current_user.id,
            "join_group",
            "{} added to group with role: {}".format(member.name, form.data["role"]),
            on_group_id=group.id,
            on_user_id=on_user_id,
        )

        if member.type == "User":
            send_email(
                self.session,
                [member.name],
                "Added to group: {}".format(group.name),
                "request_actioned",
                settings(),
                {
                    "group_name": group.name,
                    "actioned_by": self.current_user.name,
                    "reason": form.data["reason"],
                    "expiration": expiration,
                    "role": form.data["role"],
                },
            )

        return self.redirect("/groups/{}?refresh=yes".format(group.name))
Esempio n. 40
0
def get_user_view_template_vars(session, actor, user, graph):
    # type: (Session, User, User, GroupGraph) -> Dict[str, Any]
    # TODO(cbguder): get around circular dependencies
    from grouper.fe.handlers.user_disable import UserDisable
    from grouper.fe.handlers.user_enable import UserEnable

    ret = {}  # type: Dict[str, Any]
    if user.is_service_account:
        ret["can_control"] = can_manage_service_account(
            session, user.service_account, actor) or user_is_user_admin(
                session, actor)
        ret["can_disable"] = ret["can_control"]
        ret["can_enable"] = user_is_user_admin(session, actor)
        ret["can_enable_preserving_membership"] = user_is_user_admin(
            session, actor)
        ret["account"] = user.service_account
    else:
        ret["can_control"] = user.name == actor.name or user_is_user_admin(
            session, actor)
        ret["can_disable"] = UserDisable.check_access(session, actor, user)
        ret["can_enable_preserving_membership"] = UserEnable.check_access(
            session, actor, user)
        ret["can_enable"] = UserEnable.check_access_without_membership(
            session, actor, user)

    if user.id == actor.id:
        ret["num_pending_group_requests"] = user_requests_aggregate(
            session, actor).count()
        _, ret["num_pending_perm_requests"] = get_requests(session,
                                                           status="pending",
                                                           limit=1,
                                                           offset=0,
                                                           owner=actor)
    else:
        ret["num_pending_group_requests"] = None
        ret["num_pending_perm_requests"] = None

    try:
        user_md = graph.get_user_details(user.name)
    except NoSuchUser:
        # Either user is probably very new, so they have no metadata yet, or
        # they're disabled, so we've excluded them from the in-memory graph.
        user_md = {}

    shell_metadata = get_user_metadata_by_key(session, user.id,
                                              USER_METADATA_SHELL_KEY)
    ret["shell"] = shell_metadata.data_value if shell_metadata else "No shell configured"
    github_username = get_user_metadata_by_key(
        session, user.id, USER_METADATA_GITHUB_USERNAME_KEY)
    ret["github_username"] = github_username.data_value if github_username else "(Unset)"
    addl_metadata = get_user_metadata(
        session,
        user.id,
        exclude=[USER_METADATA_SHELL_KEY, USER_METADATA_GITHUB_USERNAME_KEY])

    ret["addl_metadata"] = addl_metadata if addl_metadata else []

    known_metadata_fields = set(settings().metadata_options.keys())
    set_metadata_fields = {md.data_key for md in addl_metadata}

    ret["unset_metadata"] = known_metadata_fields - set_metadata_fields

    ret["open_audits"] = user_open_audits(session, user)
    group_edge_list = get_groups_by_user(session, user) if user.enabled else []
    ret["groups"] = [{
        "name": g.name,
        "type": "Group",
        "role": ge._role
    } for g, ge in group_edge_list]
    ret["passwords"] = user_passwords(session, user)
    ret["public_keys"] = get_public_keys_of_user(session, user.id)
    ret["log_entries"] = get_log_entries_by_user(session, user)
    ret["user_tokens"] = user.tokens

    if user.is_service_account:
        service_account = user.service_account
        ret["permissions"] = service_account_permissions(
            session, service_account)
    else:
        ret["permissions"] = user_md.get("permissions", [])
        for permission in ret["permissions"]:
            permission["granted_on"] = datetime.fromtimestamp(
                permission["granted_on"])

    return ret
Esempio n. 41
0
 def forbidden(self):
     # type: () -> None
     self.set_status(403)
     self.raise_and_log_exception(tornado.web.HTTPError(403))
     self.render("errors/forbidden.html", how_to_get_help=settings().how_to_get_help)
Esempio n. 42
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        user_id = kwargs.get("user_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        user = User.get(self.session, user_id, name)
        if not user:
            return self.notfound()

        if not self.check_access(self.session, self.current_user, user):
            return self.forbidden()

        form = PublicKeyForm(self.request.arguments)
        if not form.validate():
            return self.render(
                "public-key-add.html",
                form=form,
                user=user,
                alerts=self.get_form_alerts(form.errors),
            )

        try:
            pubkey = public_key.add_public_key(self.session, user, form.data["public_key"])
        except public_key.DuplicateKey:
            form.public_key.errors.append("Key already in use. Public keys must be unique.")
        except public_key.PublicKeyParseError:
            form.public_key.errors.append("Public key appears to be invalid.")
        except public_key.BadPublicKey as e:
            form.public_key.errors.append(str(e))

        if form.public_key.errors:
            return self.render(
                "public-key-add.html",
                form=form,
                user=user,
                alerts=self.get_form_alerts(form.errors),
            )

        AuditLog.log(
            self.session,
            self.current_user.id,
            "add_public_key",
            "Added public key: {}".format(pubkey.fingerprint_sha256),
            on_user_id=user.id,
        )

        email_context = {
            "actioner": self.current_user.name,
            "changed_user": user.name,
            "action": "added",
        }
        send_email(
            self.session,
            [user.name],
            "Public SSH key added",
            "ssh_keys_changed",
            settings(),
            email_context,
        )

        return self.redirect("/users/{}?refresh=yes".format(user.name))
Esempio n. 43
0
 def forbidden(self):
     # type: () -> None
     self.set_status(403)
     self.raise_and_log_exception(tornado.web.HTTPError(403))
     self.render("errors/forbidden.html",
                 how_to_get_help=settings().how_to_get_help)
Esempio n. 44
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        request_id = kwargs["request_id"]  # type: int
        group_id = kwargs.get("group_id")  # type: Optional[int]
        name = kwargs.get("name")  # type: Optional[str]

        group = Group.get(self.session, group_id, name)
        if not group:
            return self.notfound()

        members = group.my_members()
        my_role = user_role(self.current_user, members)
        if my_role not in ("manager", "owner", "np-owner"):
            return self.forbidden()

        request = self.session.query(Request).filter_by(id=request_id).scalar()
        if not request:
            return self.notfound()

        on_behalf = get_on_behalf_by_request(self.session, request)

        form = GroupRequestModifyForm(self.request.arguments)
        form.status.choices = self._get_choices(request.status)

        updates = request.my_status_updates()

        if not form.status.choices:
            alerts = [Alert("info", "Request has already been processed")]
            return self.render(
                "group-request-update.html",
                group=group,
                request=request,
                on_behalf=on_behalf,
                members=members,
                form=form,
                alerts=alerts,
                statuses=REQUEST_STATUS_CHOICES,
                updates=updates,
            )

        if not form.validate():
            return self.render(
                "group-request-update.html",
                group=group,
                request=request,
                on_behalf=on_behalf,
                members=members,
                form=form,
                alerts=self.get_form_alerts(form.errors),
                statuses=REQUEST_STATUS_CHOICES,
                updates=updates,
            )

        # We have to test this here, too, to ensure that someone can't sneak in with a pending
        # request that used to be allowed.
        if form.data["status"] != "cancelled":
            fail_message = "This join is denied with this role at this time."
            try:
                user_can_join = assert_can_join(
                    request.requesting, on_behalf, role=request.edge.role
                )
            except UserNotAuditor as e:
                user_can_join = False
                fail_message = str(e)
            if not user_can_join:
                return self.render(
                    "group-request-update.html",
                    group=group,
                    request=request,
                    on_behalf=on_behalf,
                    members=members,
                    form=form,
                    statuses=REQUEST_STATUS_CHOICES,
                    updates=updates,
                    alerts=[Alert("danger", fail_message, "Audit Policy Enforcement")],
                )

        request.update_status(self.current_user, form.data["status"], form.data["reason"])
        self.session.commit()

        AuditLog.log(
            self.session,
            self.current_user.id,
            "update_request",
            "Updated request to status: {}".format(form.data["status"]),
            on_group_id=group.id,
            on_user_id=request.requester.id,
        )

        edge = self.session.query(GroupEdge).filter_by(id=request.edge_id).one()

        approver_mail_to = [
            user.name
            for user in group.my_approver_users()
            if user.name != self.current_user.name and user.name != request.requester.username
        ]

        subj = "Re: " + self.render_template(
            "email/pending_request_subj.tmpl", group=group.name, user=request.requester.username
        )

        send_email(
            self.session,
            approver_mail_to,
            subj,
            "approver_request_updated",
            settings(),
            {
                "group_name": group.name,
                "requester": request.requester.username,
                "changed_by": self.current_user.name,
                "status": form.data["status"],
                "role": edge.role,
                "reason": form.data["reason"],
                "references_header": request.reference_id,
            },
        )

        if form.data["status"] == "actioned":
            send_email(
                self.session,
                [request.requester.name],
                "Added to group: {}".format(group.groupname),
                "request_actioned",
                settings(),
                {
                    "group_name": group.name,
                    "actioned_by": self.current_user.name,
                    "reason": form.data["reason"],
                    "expiration": edge.expiration,
                    "role": edge.role,
                },
            )
        elif form.data["status"] == "cancelled":
            send_email(
                self.session,
                [request.requester.name],
                "Request to join cancelled: {}".format(group.groupname),
                "request_cancelled",
                settings(),
                {
                    "group_name": group.name,
                    "cancelled_by": self.current_user.name,
                    "reason": form.data["reason"],
                    "expiration": edge.expiration,
                    "role": edge.role,
                },
            )

        # No explicit refresh because handler queries SQL.
        if form.data["redirect_aggregate"]:
            return self.redirect("/user/requests")
        else:
            return self.redirect("/groups/{}/requests".format(group.name))
Esempio n. 45
0
 def baduser(self, message):
     # type: (str) -> None
     self.set_status(403)
     self.raise_and_log_exception(tornado.web.HTTPError(403))
     how_to_get_help = settings().how_to_get_help
     self.render("errors/baduser.html", message=message, how_to_get_help=how_to_get_help)
Esempio n. 46
0
 def forbidden(self) -> None:
     self.set_status(403)
     self.raise_and_log_exception(HTTPError(403))
     self.render("errors/forbidden.html",
                 how_to_get_help=settings().how_to_get_help)
Esempio n. 47
0
    def post(self, *args, **kwargs):
        # type: (*Any, **Any) -> None
        form = AuditCreateForm(self.request.arguments)
        if not form.validate():
            return self.render(
                "audit-create.html", form=form, alerts=self.get_form_alerts(form.errors)
            )

        if not user_has_permission(self.session, self.current_user, AUDIT_MANAGER):
            return self.forbidden()

        # Step 1, detect if there are non-completed audits and fail if so.
        open_audits = self.session.query(Audit).filter(Audit.complete == False).all()
        if open_audits:
            raise Exception("Sorry, there are audits in progress.")
        ends_at = datetime.strptime(form.data["ends_at"], "%m/%d/%Y")

        # Step 2, find all audited groups and schedule audits for each.
        audited_groups = []
        for groupname in self.graph.groups:
            if not self.graph.get_group_details(groupname)["audited"]:
                continue
            group = Group.get(self.session, name=groupname)
            audit = Audit(group_id=group.id, ends_at=ends_at)
            try:
                audit.add(self.session)
                self.session.flush()
            except IntegrityError:
                self.session.rollback()
                raise Exception("Failed to start the audit. Please try again.")

            # Update group with new audit
            audited_groups.append(group)
            group.audit_id = audit.id

            # Step 3, now get all members of this group and set up audit rows for those edges.
            for member in itervalues(group.my_members()):
                auditmember = AuditMember(audit_id=audit.id, edge_id=member.edge_id)
                try:
                    auditmember.add(self.session)
                except IntegrityError:
                    self.session.rollback()
                    raise Exception("Failed to start the audit. Please try again.")

        self.session.commit()

        AuditLog.log(
            self.session,
            self.current_user.id,
            "start_audit",
            "Started global audit.",
            category=AuditLogCategory.audit,
        )

        # Calculate schedule of emails, basically we send emails at various periods in advance
        # of the end of the audit period.
        schedule_times = []
        not_before = datetime.utcnow() + timedelta(1)
        for days_prior in (28, 21, 14, 7, 3, 1):
            email_time = ends_at - timedelta(days_prior)
            email_time.replace(hour=17, minute=0, second=0)
            if email_time > not_before:
                schedule_times.append((days_prior, email_time))

        # Now send some emails. We do this separately/later to ensure that the audits are all
        # created. Email notifications are sent multiple times if group audits are still
        # outstanding.
        for group in audited_groups:
            mail_to = [
                member.name
                for member in group.my_users()
                if GROUP_EDGE_ROLES[member.role] in ("owner", "np-owner")
            ]

            send_email(
                self.session,
                mail_to,
                "Group Audit: {}".format(group.name),
                "audit_notice",
                settings(),
                {"group": group.name, "ends_at": ends_at},
            )

            for days_prior, email_time in schedule_times:
                send_async_email(
                    self.session,
                    mail_to,
                    "Group Audit: {} - {} day(s) left".format(group.name, days_prior),
                    "audit_notice_reminder",
                    settings(),
                    {"group": group.name, "ends_at": ends_at, "days_left": days_prior},
                    email_time,
                    async_key="audit-{}".format(group.id),
                )

        return self.redirect("/audits")