Beispiel #1
0
    async def authenticate(self, conn: HTTPConnection):
        unauthenticated = (None, AnonymousUser())
        sid = conn.cookies.get("AURSID")
        if not sid:
            return unauthenticated

        timeout = aurweb.config.getint("options", "login_timeout")
        remembered = ("AURREMEMBER" in conn.cookies
                      and bool(conn.cookies.get("AURREMEMBER")))
        if remembered:
            timeout = aurweb.config.getint("options",
                                           "persistent_cookie_timeout")

        # If no session with sid and a LastUpdateTS now or later exists.
        now_ts = time.utcnow()
        record = db.query(Session).filter(Session.SessionID == sid).first()
        if not record:
            return unauthenticated
        elif record.LastUpdateTS < (now_ts - timeout):
            with db.begin():
                db.delete_all([record])
            return unauthenticated

        # At this point, we cannot have an invalid user if the record
        # exists, due to ForeignKey constraints in the schema upheld
        # by mysqlclient.
        with db.begin():
            user = db.query(User).filter(User.ID == record.UsersID).first()
        user.nonce = util.make_nonce()
        user.authenticated = True

        return (AuthCredentials(["authenticated"]), user)
Beispiel #2
0
def test_usermaint(user: User):
    """
    In this case, we first test that only the expired record gets
    updated, but the non-expired record remains untouched. After,
    we update the login time on the non-expired record and exercise
    its code path.
    """

    now = time.utcnow()
    limit_to = now - 86400 * 7
    with db.begin():
        user.LastLoginIPAddress = "127.0.0.1"
        user.LastLogin = limit_to - 666
        user.LastSSHLoginIPAddress = "127.0.0.1"
        user.LastSSHLogin = now - 10

    usermaint.main()

    assert user.LastLoginIPAddress is None
    assert user.LastSSHLoginIPAddress == "127.0.0.1"

    with db.begin():
        user.LastSSHLogin = limit_to - 666

    usermaint.main()

    assert user.LastLoginIPAddress is None
    assert user.LastSSHLoginIPAddress is None
Beispiel #3
0
async def request_close_post(request: Request,
                             id: int,
                             comments: str = Form(default=str())):
    pkgreq = get_pkgreq_by_id(id)

    # `pkgreq`.User can close their own request.
    approved = [pkgreq.User]
    if not request.user.has_credential(creds.PKGREQ_CLOSE, approved=approved):
        # Request user doesn't have permission here: redirect to '/'.
        return RedirectResponse("/", status_code=HTTPStatus.SEE_OTHER)

    context = make_context(request, "Close Request")
    context["pkgreq"] = pkgreq

    now = time.utcnow()
    with db.begin():
        pkgreq.Closer = request.user
        pkgreq.ClosureComment = comments
        pkgreq.ClosedTS = now
        pkgreq.Status = REJECTED_ID

    notify_ = notify.RequestCloseNotification(request.user.ID, pkgreq.ID,
                                              pkgreq.status_display())
    notify_.send()

    return RedirectResponse("/requests", status_code=HTTPStatus.SEE_OTHER)
Beispiel #4
0
def test_homepage_dashboard_flagged(user: User, user2: User, package: Package):
    pkgbase = package.PackageBase

    now = time.utcnow()
    with db.begin():
        db.create(PackageComaintainer,
                  User=user2,
                  PackageBase=pkgbase,
                  Priority=1)
        pkgbase.OutOfDateTS = now - 5
        pkgbase.Flagger = user

    # Test that a comaintainer viewing the dashboard shows them their
    # flagged co-maintained packages.
    comaint_cookies = {"AURSID": user2.login(Request(), "testPassword")}
    with client as request:
        resp = request.get("/", cookies=comaint_cookies)
    assert resp.status_code == int(HTTPStatus.OK)

    root = parse_root(resp.text)
    flagged = root.xpath('//table[@id="flagged-packages"]//tr/td/a')[0]
    assert flagged.text.strip() == package.Name

    # Test that a maintainer viewing the dashboard shows them their
    # flagged maintained packages.
    cookies = {"AURSID": user.login(Request(), "testPassword")}
    with client as request:
        resp = request.get("/", cookies=cookies)
    assert resp.status_code == int(HTTPStatus.OK)

    root = parse_root(resp.text)
    flagged = root.xpath('//table[@id="flagged-packages"]//tr/td/a')[0]
    assert flagged.text.strip() == package.Name
Beispiel #5
0
def make_context(request: Request, title: str, next: str = None):
    """ Create a context for a jinja2 TemplateResponse. """
    import aurweb.auth.creds

    commit_url = aurweb.config.get_with_fallback("devel", "commit_url", None)
    commit_hash = aurweb.config.get_with_fallback("devel", "commit_hash", None)
    if commit_hash:
        # Shorten commit_hash to a short Git hash.
        commit_hash = commit_hash[:7]

    timezone = time.get_request_timezone(request)
    language = l10n.get_request_language(request)
    return {
        "request": request,
        "commit_url": commit_url,
        "commit_hash": commit_hash,
        "language": language,
        "languages": l10n.SUPPORTED_LANGUAGES,
        "rtl": language in l10n.RIGHT_TO_LEFT_LANGUAGES,
        "timezone": timezone,
        "timezones": time.SUPPORTED_TIMEZONES,
        "title": title,
        "now": time.now(timezone),
        "utcnow": time.utcnow(),
        "config": aurweb.config,
        "creds": aurweb.auth.creds,
        "next": next if next else request.url.path,
        "version": os.environ.get("COMMIT_HASH", aurweb.config.AURWEB_VERSION)
    }
Beispiel #6
0
async def pkgbase_comment_pin(request: Request,
                              name: str,
                              id: int,
                              next: str = Form(default=None)):
    """
    Pin a comment.

    :param request: FastAPI Request
    :param name: PackageBase.Name
    :param id: PackageComment.ID
    :param next: Optional `next` parameter used in the POST request
    :return: RedirectResponse to `next`
    """
    pkgbase = get_pkg_or_base(name, PackageBase)
    comment = get_pkgbase_comment(pkgbase, id)

    has_cred = request.user.has_credential(creds.COMMENT_PIN,
                                           approved=comment.maintainers())
    if not has_cred:
        _ = l10n.get_translator_for_request(request)
        raise HTTPException(
            status_code=HTTPStatus.UNAUTHORIZED,
            detail=_("You are not allowed to pin this comment."))

    now = time.utcnow()
    with db.begin():
        comment.PinnedTS = now

    if not next:
        next = f"/pkgbase/{name}"

    return RedirectResponse(next, status_code=HTTPStatus.SEE_OTHER)
Beispiel #7
0
def test_request_post_orphan_autogenerated_closure(client: TestClient,
                                                   tu_user: User,
                                                   pkgbase: PackageBase,
                                                   pkgreq: PackageRequest):
    idle_time = config.getint("options", "request_idle_time")
    now = time.utcnow()
    with db.begin():
        pkgreq.ReqTypeID = ORPHAN_ID
        # Set the request time so it's seen as due (idle_time has passed).
        pkgreq.RequestTS = now - idle_time - 10

    endpoint = f"/pkgbase/{pkgbase.Name}/disown"
    data = {"confirm": True}
    with client as request:
        resp = request.post(endpoint, data=data, cookies=tu_user.cookies)
    assert resp.status_code == int(HTTPStatus.SEE_OTHER)
    assert resp.headers.get("location") == f"/pkgbase/{pkgbase.Name}"

    assert Email.count() == 1
    email = Email(1)
    expr = r"^\[PRQ#\d+\] Orphan Request for .+ Accepted$"
    assert re.match(expr, email.headers.get("Subject"))

    expr = r"\[Autogenerated\] Accepted orphan for .+\."
    assert re.search(expr, email.body)
Beispiel #8
0
async def pkgbase_comments_post(
    request: Request,
    name: str,
    comment: str = Form(default=str()),
    enable_notifications: bool = Form(default=False)):
    """ Add a new comment via POST request. """
    pkgbase = get_pkg_or_base(name, PackageBase)

    if not comment:
        raise HTTPException(status_code=HTTPStatus.BAD_REQUEST)

    # If the provided comment is different than the record's version,
    # update the db record.
    now = time.utcnow()
    with db.begin():
        comment = db.create(PackageComment,
                            User=request.user,
                            PackageBase=pkgbase,
                            Comments=comment,
                            RenderedComment=str(),
                            CommentTS=now)

        if enable_notifications and not request.user.notified(pkgbase):
            db.create(PackageNotification,
                      User=request.user,
                      PackageBase=pkgbase)
    update_comment_render_fastapi(comment)

    notif = notify.CommentNotification(request.user.ID, pkgbase.ID, comment.ID)
    notif.send()

    # Redirect to the pkgbase page.
    return RedirectResponse(f"/pkgbase/{pkgbase.Name}#comment-{comment.ID}",
                            status_code=HTTPStatus.SEE_OTHER)
Beispiel #9
0
async def pkgbase_flag_post(request: Request,
                            name: str,
                            comments: str = Form(default=str())):
    pkgbase = get_pkg_or_base(name, PackageBase)

    if not comments:
        context = templates.make_context(request, "Flag Package Out-Of-Date")
        context["pkgbase"] = pkgbase
        context["errors"] = [
            "The selected packages have not been flagged, "
            "please enter a comment."
        ]
        return render_template(request,
                               "pkgbase/flag.html",
                               context,
                               status_code=HTTPStatus.BAD_REQUEST)

    has_cred = request.user.has_credential(creds.PKGBASE_FLAG)
    if has_cred and not pkgbase.OutOfDateTS:
        now = time.utcnow()
        with db.begin():
            pkgbase.OutOfDateTS = now
            pkgbase.Flagger = request.user
            pkgbase.FlaggerComment = comments

        notify.FlagNotification(request.user.ID, pkgbase.ID).send()

    return RedirectResponse(f"/pkgbase/{name}",
                            status_code=HTTPStatus.SEE_OTHER)
Beispiel #10
0
def simple(U: str = str(),
           E: str = str(),
           H: bool = False,
           BE: str = str(),
           R: str = str(),
           HP: str = str(),
           I: str = str(),
           K: str = str(),
           J: bool = False,
           CN: bool = False,
           UN: bool = False,
           ON: bool = False,
           S: bool = False,
           user: models.User = None,
           **kwargs) -> None:
    now = time.utcnow()
    with db.begin():
        user.Username = U or user.Username
        user.Email = E or user.Email
        user.HideEmail = strtobool(H)
        user.BackupEmail = user.BackupEmail if BE is None else BE
        user.RealName = user.RealName if R is None else R
        user.Homepage = user.Homepage if HP is None else HP
        user.IRCNick = user.IRCNick if I is None else I
        user.PGPKey = user.PGPKey if K is None else K
        user.Suspended = strtobool(S)
        user.InactivityTS = now * int(strtobool(J))
        user.CommentNotify = strtobool(CN)
        user.UpdateNotify = strtobool(UN)
        user.OwnershipNotify = strtobool(ON)
Beispiel #11
0
def _main():
    # One day behind.
    limit_to = time.utcnow() - 86400

    query = db.query(PackageBase).filter(
        and_(PackageBase.SubmittedTS < limit_to,
             PackageBase.PackagerUID.is_(None)))
    db.delete_all(query)
Beispiel #12
0
def pkgbase(user: User) -> PackageBase:
    now = time.utcnow()
    with db.begin():
        pkgbase = db.create(PackageBase,
                            Name="test-pkg",
                            Maintainer=user,
                            SubmittedTS=now,
                            ModifiedTS=now)
    yield pkgbase
def pkgbase(user: User) -> PackageBase:
    now = time.utcnow()
    with db.begin():
        pkgbase = db.create(PackageBase,
                            Packager=user,
                            Name="pkgbase_0",
                            SubmittedTS=now,
                            ModifiedTS=now)
    yield pkgbase
Beispiel #14
0
def test_generate_unique_sid_exhausted(client: TestClient, user: User,
                                       caplog: pytest.LogCaptureFixture):
    """
    In this test, we mock up generate_unique_sid() to infinitely return
    the same SessionID given to `user`. Within that mocking, we try
    to login as `user2` and expect the internal server error rendering
    by our error handler.

    This exercises the bad path of /login, where we can't find a unique
    SID to assign the user.
    """
    now = time.utcnow()
    with db.begin():
        # Create a second user; we'll login with this one.
        user2 = db.create(User,
                          Username="******",
                          Email="*****@*****.**",
                          ResetKey="testReset",
                          Passwd="testPassword",
                          AccountTypeID=USER_ID)

        # Create a session with ID == "testSession" for `user`.
        db.create(Session,
                  User=user,
                  SessionID="testSession",
                  LastUpdateTS=now)

    # Mock out generate_unique_sid; always return "testSession" which
    # causes us to eventually error out and raise an internal error.
    def mock_generate_sid():
        return "testSession"

    # Login as `user2`; we expect an internal server error response
    # with a relevent detail.
    post_data = {
        "user": user2.Username,
        "passwd": "testPassword",
        "next": "/",
    }
    generate_unique_sid_ = "aurweb.models.session.generate_unique_sid"
    with mock.patch(generate_unique_sid_, mock_generate_sid):
        with client as request:
            # Set cookies = {} to remove any previous login kept by TestClient.
            response = request.post("/login", data=post_data, cookies={})
    assert response.status_code == int(HTTPStatus.INTERNAL_SERVER_ERROR)

    assert "500 - Internal Server Error" in response.text

    # Make sure an IntegrityError from the DB got logged out
    # with a FATAL traceback ID.
    expr = r"FATAL\[.{7}\]"
    assert re.search(expr, caplog.text)
    assert "IntegrityError" in caplog.text

    expr = r"Duplicate entry .+ for key .+SessionID.+"
    assert re.search(expr, response.text)
Beispiel #15
0
def _main():
    limit_to = time.utcnow() - 86400 * 7

    update_ = update(User).where(User.LastLogin < limit_to).values(
        LastLoginIPAddress=None)
    db.get_session().execute(update_)

    update_ = update(User).where(User.LastSSHLogin < limit_to).values(
        LastSSHLoginIPAddress=None)
    db.get_session().execute(update_)
Beispiel #16
0
def tu_voteinfo(user: User) -> TUVoteInfo:
    ts = time.utcnow()
    with db.begin():
        tu_voteinfo = db.create(TUVoteInfo,
                                Agenda="Blah blah.",
                                User=user.Username,
                                Submitted=ts,
                                End=ts + 5,
                                Quorum=0.5,
                                Submitter=user)
    yield tu_voteinfo
Beispiel #17
0
def test_query_voted(maintainer: User, package: Package):
    now = time.utcnow()
    with db.begin():
        db.create(PackageVote,
                  User=maintainer,
                  VoteTS=now,
                  PackageBase=package.PackageBase)

    query = db.query(Package).filter(Package.ID == package.ID).all()
    query_voted = util.query_voted(query, maintainer)
    assert query_voted[package.PackageBase.ID]
Beispiel #18
0
def voteinfo(user: User) -> TUVoteInfo:
    now = time.utcnow()
    start = config.getint("tuvotereminder", "range_start")
    with db.begin():
        voteinfo = db.create(TUVoteInfo,
                             Agenda="Lorem ipsum.",
                             User=user.Username,
                             End=(now + start + 1),
                             Quorum=0.00,
                             Submitter=user,
                             Submitted=0)
    yield voteinfo
def test_package_vote_creation(user: User, pkgbase: PackageBase):
    ts = time.utcnow()

    with db.begin():
        package_vote = db.create(PackageVote,
                                 User=user,
                                 PackageBase=pkgbase,
                                 VoteTS=ts)
    assert bool(package_vote)
    assert package_vote.User == user
    assert package_vote.PackageBase == pkgbase
    assert package_vote.VoteTS == ts
Beispiel #20
0
def packages(user):
    pkgs = []
    now = time.utcnow()

    # Create 101 packages; we limit 100 on RSS feeds.
    with db.begin():
        for i in range(101):
            pkgbase = db.create(
                PackageBase, Maintainer=user, Name=f"test-package-{i}",
                SubmittedTS=(now + i), ModifiedTS=(now + i))
            pkg = db.create(Package, Name=pkgbase.Name, PackageBase=pkgbase)
            pkgs.append(pkg)
    yield pkgs
Beispiel #21
0
def verify_orphan_request(user: User, pkgbase: PackageBase):
    """ Verify that an undue orphan request exists in `requests`. """
    requests = pkgbase.requests.filter(PackageRequest.ReqTypeID == ORPHAN_ID)
    for pkgreq in requests:
        idle_time = config.getint("options", "request_idle_time")
        time_delta = time.utcnow() - pkgreq.RequestTS
        is_due = pkgreq.Status == PENDING_ID and time_delta > idle_time
        if is_due:
            # If the requester is the pkgbase maintainer or the
            # request is already due, we're good to go: return True.
            return True

    return False
Beispiel #22
0
def test_session_cs():
    """ Test case sensitivity of the database table. """
    with db.begin():
        user2 = db.create(User, Username="******", Email="*****@*****.**",
                          ResetKey="testReset2", Passwd="testPassword",
                          AccountTypeID=USER_ID)

    with db.begin():
        session_cs = db.create(Session, User=user2, SessionID="TESTSESSION",
                               LastUpdateTS=time.utcnow())

    assert session_cs.SessionID == "TESTSESSION"
    assert session_cs.SessionID != "testSession"
Beispiel #23
0
    def login(self, request: Request, password: str,
              session_time: int = 0) -> str:
        """ Login and authenticate a request. """

        from aurweb import db
        from aurweb.models.session import Session, generate_unique_sid

        if not self._login_approved(request):
            return None

        self.authenticated = self.valid_password(password)
        if not self.authenticated:
            return None

        # Maximum number of iterations where we attempt to generate
        # a unique SID. In cases where the Session table has
        # exhausted all possible values, this will catch exceptions
        # instead of raising them and include details about failing
        # generation in an HTTPException.
        tries = 36

        exc = None
        for i in range(tries):
            exc = None
            now_ts = time.utcnow()
            try:
                with db.begin():
                    self.LastLogin = now_ts
                    self.LastLoginIPAddress = request.client.host
                    if not self.session:
                        sid = generate_unique_sid()
                        self.session = db.create(Session, User=self,
                                                 SessionID=sid,
                                                 LastUpdateTS=now_ts)
                    else:
                        last_updated = self.session.LastUpdateTS
                        if last_updated and last_updated < now_ts:
                            self.session.SessionID = generate_unique_sid()
                        self.session.LastUpdateTS = now_ts

                    # Unset InactivityTS, we've logged in!
                    self.InactivityTS = 0

                    break
            except IntegrityError as exc_:
                exc = exc_

        if exc:
            raise exc

        return self.session.SessionID
Beispiel #24
0
def test_usermaint_noop(user: User):
    """ Last[SSH]Login isn't expired in this test: usermaint is noop. """

    now = time.utcnow()
    with db.begin():
        user.LastLoginIPAddress = "127.0.0.1"
        user.LastLogin = now - 10
        user.LastSSHLoginIPAddress = "127.0.0.1"
        user.LastSSHLogin = now - 10

    usermaint.main()

    assert user.LastLoginIPAddress == "127.0.0.1"
    assert user.LastSSHLoginIPAddress == "127.0.0.1"
Beispiel #25
0
async def test_basic_auth_backend(user: User, backend: BasicAuthBackend):
    # This time, everything matches up. We expect the user to
    # equal the real_user.
    now_ts = time.utcnow()
    with db.begin():
        db.create(Session,
                  UsersID=user.ID,
                  SessionID="realSession",
                  LastUpdateTS=now_ts + 5)

    request = Request()
    request.cookies["AURSID"] = "realSession"
    _, result = await backend.authenticate(request)
    assert result == user
def proposal(user, tu_user):
    ts = time.utcnow()
    agenda = "Test proposal."
    start = ts - 5
    end = ts + 1000

    with db.begin():
        voteinfo = db.create(TUVoteInfo,
                             Agenda=agenda,
                             Quorum=0.0,
                             User=user.Username,
                             Submitter=tu_user,
                             Submitted=start,
                             End=end)
    yield (tu_user, user, voteinfo)
Beispiel #27
0
def test_tu_voteinfo_is_running(user: User):
    ts = time.utcnow()
    with db.begin():
        tu_voteinfo = create(TUVoteInfo,
                             Agenda="Blah blah.",
                             User=user.Username,
                             Submitted=ts,
                             End=ts + 1000,
                             Quorum=0.5,
                             Submitter=user)
    assert tu_voteinfo.is_running() is True

    with db.begin():
        tu_voteinfo.End = ts - 5
    assert tu_voteinfo.is_running() is False
Beispiel #28
0
def main():
    db.get_engine()

    now = time.utcnow()

    start = aurweb.config.getint("tuvotereminder", "range_start")
    filter_from = now + start

    end = aurweb.config.getint("tuvotereminder", "range_end")
    filter_to = now + end

    query = db.query(TUVoteInfo.ID).filter(
        and_(TUVoteInfo.End >= filter_from, TUVoteInfo.End <= filter_to))
    for voteinfo in query:
        notif = notify.TUVoteReminderNotification(voteinfo.ID)
        notif.send()
Beispiel #29
0
def packages(maintainer: User) -> List[Package]:
    """ Yield 55 packages named pkg_0 .. pkg_54. """
    packages_ = []
    now = time.utcnow()
    with db.begin():
        for i in range(55):
            pkgbase = db.create(PackageBase,
                                Name=f"pkg_{i}",
                                Maintainer=maintainer,
                                Packager=maintainer,
                                Submitter=maintainer,
                                ModifiedTS=now)
            package = db.create(Package, PackageBase=pkgbase, Name=f"pkg_{i}")
            packages_.append(package)

    yield packages_
Beispiel #30
0
def test_homepage_dashboard_flagged_packages(redis, packages, user):
    # Set the first Package flagged by setting its OutOfDateTS column.
    pkg = packages[0]
    with db.begin():
        pkg.PackageBase.OutOfDateTS = time.utcnow()

    cookies = {"AURSID": user.login(Request(), "testPassword")}
    with client as request:
        response = request.get("/", cookies=cookies)
    assert response.status_code == int(HTTPStatus.OK)

    # Check to see that the package showed up in the Flagged Packages table.
    root = parse_root(response.text)
    flagged_pkg = root.xpath('//table[@id="flagged-packages"]/tbody/tr').pop(0)
    flagged_name = flagged_pkg.xpath('./td/a').pop(0)
    assert flagged_name.text.strip() == pkg.Name