예제 #1
0
    def test_hooks(self):
        # TODO Replace these with mocks ...
        def pre_login_test(username):
            assert isinstance(username, str)

        async def pre_login_test_async(username):
            assert isinstance(username, str)

        def login_success_test(username, user_id):
            assert isinstance(username, str)
            assert isinstance(user_id, int)

        async def login_success_test_async(username, user_id):
            assert isinstance(username, str)
            assert isinstance(user_id, int)

        def login_failure_test(username):
            assert isinstance(username, str)

        def login_failure_test_async(username):
            assert isinstance(username, str)

        router = Router(
            routes=[
                Route(
                    "/login/",
                    session_login(
                        hooks=LoginHooks(
                            pre_login=[pre_login_test, pre_login_test_async],
                            login_success=[
                                login_success_test,
                                login_success_test_async,
                            ],
                            login_failure=[
                                login_failure_test,
                                login_failure_test_async,
                            ],
                        )
                    ),
                ),
            ]
        )
        app = ExceptionMiddleware(router)

        BaseUser(**self.credentials, active=True).save().run_sync()

        client = TestClient(app)
        client.post("/login/", json=self.credentials)
예제 #2
0
 def test_simple_custom_login_template(self):
     """
     Make sure that a custom login template can be used.
     """
     template_path = os.path.join(
         os.path.dirname(__file__),
         "templates",
         "simple_login_template",
         "login.html",
     )
     router = Router(routes=[
         Route(
             "/login/",
             session_login(template_path=template_path),
         ),
     ])
     app = ExceptionMiddleware(router)
     client = TestClient(app)
     response = client.get("/login/")
     self.assertEqual(response.content, b"<p>Hello world</p>")
예제 #3
0
    def test_styles(self):
        """
        Make sure the custom styles are shown in the HTML output.
        """
        custom_styles = Styles(background_color="black")
        app = Router(routes=[
            Route(
                "/login/",
                session_login(styles=custom_styles),
            ),
            Route(
                "/logout/",
                session_logout(styles=custom_styles),
            ),
            Route(
                "/register/",
                register(styles=custom_styles),
            ),
        ])
        client = TestClient(app)

        for url in ("/login/", "/logout/", "/register/"):
            response = client.get(url)
            self.assertTrue(b"--background_color: black;" in response.content)
예제 #4
0
    def __init__(
        self,
        *tables: t.Type[Table],
        auth_table: t.Type[BaseUser] = BaseUser,
        session_table: t.Type[SessionsBase] = SessionsBase,
        session_expiry: timedelta = timedelta(hours=1),
        max_session_expiry: timedelta = timedelta(days=7),
        increase_expiry: t.Optional[timedelta] = timedelta(minutes=20),
        page_size: int = 15,
        read_only: bool = False,
        rate_limit_provider: t.Optional[RateLimitProvider] = None,
        production: bool = False,
        site_name: str = "Piccolo Admin",
    ) -> None:
        super().__init__(title=site_name,
                         description="Piccolo API documentation")

        self.auth_table = auth_table
        self.site_name = site_name
        self.tables = tables

        with open(os.path.join(ASSET_PATH, "index.html")) as f:
            self.template = f.read()

        #######################################################################

        api_app = FastAPI()

        for table in tables:
            FastAPIWrapper(
                root_url=f"/tables/{table._meta.tablename}/",
                fastapi_app=api_app,
                piccolo_crud=PiccoloCRUD(table=table,
                                         read_only=read_only,
                                         page_size=page_size),
                fastapi_kwargs=FastAPIKwargs(all_routes={
                    "tags": [f"{table._meta.tablename.capitalize()}"]
                }, ),
            )

        api_app.add_api_route(
            path="/tables/",
            endpoint=self.get_table_list,
            methods=["GET"],
            response_model=t.List[str],
            tags=["Tables"],
        )

        api_app.add_api_route(
            path="/meta/",
            endpoint=self.get_meta,
            methods=["GET"],
            tags=["Meta"],
            response_model=MetaResponseModel,
        )

        api_app.add_api_route(
            path="/user/",
            endpoint=self.get_user,
            methods=["GET"],
            tags=["User"],
            response_model=UserResponseModel,
        )

        #######################################################################

        auth_app = FastAPI()

        if not rate_limit_provider:
            rate_limit_provider = InMemoryLimitProvider(limit=1000,
                                                        timespan=300)

        auth_app.mount(
            path="/login/",
            app=RateLimitingMiddleware(
                app=session_login(
                    auth_table=self.auth_table,
                    session_table=session_table,
                    session_expiry=session_expiry,
                    max_session_expiry=max_session_expiry,
                    redirect_to=None,
                    production=production,
                ),
                provider=rate_limit_provider,
            ),
        )

        auth_app.add_route(
            path="/logout/",
            route=session_logout(session_table=session_table),
            methods=["POST"],
        )

        #######################################################################

        self.router.add_route(path="/",
                              endpoint=self.get_root,
                              methods=["GET"])

        self.mount(
            path="/css",
            app=StaticFiles(directory=os.path.join(ASSET_PATH, "css")),
        )

        self.mount(
            path="/js",
            app=StaticFiles(directory=os.path.join(ASSET_PATH, "js")),
        )

        auth_middleware = partial(
            AuthenticationMiddleware,
            backend=SessionsAuthBackend(
                auth_table=auth_table,
                session_table=session_table,
                admin_only=True,
                increase_expiry=increase_expiry,
            ),
            on_error=handle_auth_exception,
        )

        self.mount(path="/api", app=auth_middleware(api_app))
        self.mount(path="/auth", app=auth_app)
예제 #5
0
# The main app with public endpoints, which will be served by Uvicorn
app = FastAPI(
    routes=[
        # If we want to use Piccolo admin:
        Mount(
            "/admin/",
            create_admin(
                tables=[Task],
                # Required when running under HTTPS:
                # allowed_hosts=['my_site.com']
            ),
        ),
        # Session Auth login:
        Mount(
            "/login/",
            session_login(redirect_to="/private/docs/"),
        ),
    ],
    docs_url=None,
    redoc_url=None,
)

# The private app with SessionAuth protected endpoints
private_app = FastAPI(
    routes=[
        Route("/logout/", session_logout(redirect_to="/")),
        # We use a custom Swagger docs endpoint instead of the default FastAPI
        # one, because this one supports CSRF middleware:
        Mount("/docs/", swagger_ui(schema_url="/private/openapi.json")),
    ],
    middleware=[
예제 #6
0
    def __init__(
        self,
        *tables: t.Type[Table],
        auth_table: t.Type[BaseUser] = BaseUser,
        session_table: t.Type[SessionsBase] = SessionsBase,
        session_expiry: timedelta = timedelta(hours=1),
        max_session_expiry: timedelta = timedelta(days=7),
        increase_expiry: t.Optional[timedelta] = timedelta(minutes=20),
        page_size: int = 15,
        read_only: bool = False,
        rate_limit_provider: t.Optional[RateLimitProvider] = None,
        production: bool = False,
    ) -> None:
        self.auth_table = auth_table

        with open(os.path.join(ASSET_PATH, "index.html")) as f:
            self.template = f.read()

        auth_middleware = partial(
            AuthenticationMiddleware,
            backend=SessionsAuthBackend(
                auth_table=auth_table,
                session_table=session_table,
                admin_only=True,
                increase_expiry=increase_expiry,
            ),
            on_error=handle_auth_exception,
        )

        if not rate_limit_provider:
            rate_limit_provider = InMemoryLimitProvider(limit=1000,
                                                        timespan=300)

        table_routes: t.List[BaseRoute] = [
            Mount(
                path=f"/{table._meta.tablename}/",
                app=PiccoloCRUD(table,
                                read_only=read_only,
                                page_size=page_size),
            ) for table in tables
        ]
        table_routes += [
            Route(
                path="/",
                endpoint=self.get_table_list,
                methods=["GET"],
            )
        ]

        routes: t.List[BaseRoute] = [
            Route(path="/", endpoint=self.get_root, methods=["GET"]),
            Mount(
                path="/css",
                app=StaticFiles(directory=os.path.join(ASSET_PATH, "css")),
            ),
            Mount(
                path="/js",
                app=StaticFiles(directory=os.path.join(ASSET_PATH, "js")),
            ),
            Mount(
                path="/api",
                app=Router([
                    Mount(
                        path="/tables/",
                        app=auth_middleware(Router(table_routes)),
                    ),
                    Route(
                        path="/login/",
                        endpoint=RateLimitingMiddleware(
                            app=session_login(
                                auth_table=self.auth_table,
                                session_table=session_table,
                                session_expiry=session_expiry,
                                max_session_expiry=max_session_expiry,
                                redirect_to=None,
                                production=production,
                            ),
                            provider=rate_limit_provider,
                        ),
                        methods=["POST"],
                    ),
                    Route(
                        path="/logout/",
                        endpoint=session_logout(session_table=session_table),
                        methods=["POST"],
                    ),
                    Mount(
                        path="/user/",
                        app=auth_middleware(
                            Router([Route(path="/", endpoint=self.get_user)])),
                    ),
                    Mount(
                        path="/meta/",
                        app=auth_middleware(
                            Router([Route(path="/", endpoint=self.get_meta)])),
                    ),
                ]),
            ),
        ]

        self.tables = tables
        super().__init__(routes)
예제 #7
0
            return PlainTextResponse(f"hello {session_user['username']}")
        else:
            return PlainTextResponse("hello world")


class ProtectedEndpoint(HTTPEndpoint):
    @requires("authenticated", redirect="login")
    def get(self, request):
        return PlainTextResponse("top secret")


ROUTER = Router(routes=[
    Route("/", HomeEndpoint, name="home"),
    Route(
        "/login/",
        session_login(),
        name="login",
    ),
    Route("/logout/", session_logout(), name="login"),
    Mount(
        "/secret/",
        AuthenticationMiddleware(
            ProtectedEndpoint,
            SessionsAuthBackend(
                admin_only=True, superuser_only=True, active_only=True),
        ),
    ),
])
APP = ExceptionMiddleware(ROUTER)

###############################################################################
예제 #8
0
    def __init__(
        self,
        *tables: t.Union[t.Type[Table], TableConfig],
        forms: t.List[FormConfig] = [],
        auth_table: t.Type[BaseUser] = BaseUser,
        session_table: t.Type[SessionsBase] = SessionsBase,
        session_expiry: timedelta = timedelta(hours=1),
        max_session_expiry: timedelta = timedelta(days=7),
        increase_expiry: t.Optional[timedelta] = timedelta(minutes=20),
        page_size: int = 15,
        read_only: bool = False,
        rate_limit_provider: t.Optional[RateLimitProvider] = None,
        production: bool = False,
        site_name: str = "Piccolo Admin",
    ) -> None:
        super().__init__(
            title=site_name, description="Piccolo API documentation"
        )

        #######################################################################
        # Convert any table arguments which are plain ``Table`` classes into
        # ``TableConfig`` instances.

        table_configs: t.List[TableConfig] = []

        for table in tables:
            if isinstance(table, TableConfig):
                table_configs.append(table)
            else:
                table_configs.append(TableConfig(table_class=table))

        self.table_configs = table_configs

        for table_config in table_configs:
            table_class = table_config.table_class
            for column in table_class._meta.columns:
                if column._meta.secret and column._meta.required:
                    message = (
                        f"{table_class._meta.tablename}."
                        f"{column._meta._name} is using `secret` and "
                        f"`required` column args which are incompatible. "
                        f"You may encounter unexpected behavior when using "
                        f"this table within Piccolo Admin."
                    )
                    colored_warning(message, level=Level.high)

        #######################################################################

        self.auth_table = auth_table
        self.site_name = site_name
        self.forms = forms
        self.form_config_map = {form.slug: form for form in self.forms}

        with open(os.path.join(ASSET_PATH, "index.html")) as f:
            self.template = f.read()

        #######################################################################

        api_app = FastAPI(docs_url=None)
        api_app.mount("/docs/", swagger_ui(schema_url="../openapi.json"))

        for table_config in table_configs:
            table_class = table_config.table_class
            visible_column_names = table_config.get_visible_column_names()
            visible_filter_names = table_config.get_visible_filter_names()
            rich_text_columns_names = (
                table_config.get_rich_text_columns_names()
            )
            FastAPIWrapper(
                root_url=f"/tables/{table_class._meta.tablename}/",
                fastapi_app=api_app,
                piccolo_crud=PiccoloCRUD(
                    table=table_class,
                    read_only=read_only,
                    page_size=page_size,
                    schema_extra={
                        "visible_column_names": visible_column_names,
                        "visible_filter_names": visible_filter_names,
                        "rich_text_columns": rich_text_columns_names,
                    },
                ),
                fastapi_kwargs=FastAPIKwargs(
                    all_routes={
                        "tags": [f"{table_class._meta.tablename.capitalize()}"]
                    },
                ),
            )

        api_app.add_api_route(
            path="/tables/",
            endpoint=self.get_table_list,  # type: ignore
            methods=["GET"],
            response_model=t.List[str],
            tags=["Tables"],
        )

        api_app.add_api_route(
            path="/meta/",
            endpoint=self.get_meta,  # type: ignore
            methods=["GET"],
            tags=["Meta"],
            response_model=MetaResponseModel,
        )

        api_app.add_api_route(
            path="/forms/",
            endpoint=self.get_forms,  # type: ignore
            methods=["GET"],
            tags=["Forms"],
            response_model=t.List[FormConfigResponseModel],
        )

        api_app.add_api_route(
            path="/forms/{form_slug:str}/",
            endpoint=self.get_single_form,  # type: ignore
            methods=["GET"],
            tags=["Forms"],
        )

        api_app.add_api_route(
            path="/forms/{form_slug:str}/schema/",
            endpoint=self.get_single_form_schema,  # type: ignore
            methods=["GET"],
            tags=["Forms"],
        )

        api_app.add_api_route(
            path="/forms/{form_slug:str}/",
            endpoint=self.post_single_form,  # type: ignore
            methods=["POST"],
            tags=["Forms"],
        )

        api_app.add_api_route(
            path="/user/",
            endpoint=self.get_user,  # type: ignore
            methods=["GET"],
            tags=["User"],
            response_model=UserResponseModel,
        )

        #######################################################################

        auth_app = FastAPI()

        if not rate_limit_provider:
            rate_limit_provider = InMemoryLimitProvider(
                limit=1000, timespan=300
            )

        auth_app.mount(
            path="/login/",
            app=RateLimitingMiddleware(
                app=session_login(
                    auth_table=self.auth_table,
                    session_table=session_table,
                    session_expiry=session_expiry,
                    max_session_expiry=max_session_expiry,
                    redirect_to=None,
                    production=production,
                ),
                provider=rate_limit_provider,
            ),
        )

        auth_app.add_route(
            path="/logout/",
            route=session_logout(session_table=session_table),
            methods=["POST"],
        )

        #######################################################################

        self.router.add_route(
            path="/", endpoint=self.get_root, methods=["GET"]
        )

        self.mount(
            path="/css",
            app=StaticFiles(directory=os.path.join(ASSET_PATH, "css")),
        )

        self.mount(
            path="/js",
            app=StaticFiles(directory=os.path.join(ASSET_PATH, "js")),
        )

        auth_middleware = partial(
            AuthenticationMiddleware,
            backend=SessionsAuthBackend(
                auth_table=auth_table,
                session_table=session_table,
                admin_only=True,
                increase_expiry=increase_expiry,
            ),
            on_error=handle_auth_exception,
        )

        self.mount(path="/api", app=auth_middleware(api_app))
        self.mount(path="/auth", app=auth_app)

        # We make the meta endpoint available without auth, because it contains
        # the site name.
        self.add_api_route("/meta/", endpoint=self.get_meta)  # type: ignore
예제 #9
0
private_app = Starlette(
    routes=[
        Route(
            "/change-password/",
            change_password(),
        ),
    ],
    middleware=[
        Middleware(
            AuthenticationMiddleware,
            on_error=on_auth_error,
            backend=SessionsAuthBackend(admin_only=False),
        ),
    ],
)

app = Starlette(
    routes=[
        Route("/", HomeEndpoint),
        Route("/login/", session_login()),
        Route(
            "/register/",
            register(redirect_to="/login/", user_defaults={"active": True}),
        ),
        Mount("/private/", private_app),
    ],
    middleware=[
        Middleware(CSRFMiddleware, allow_form_param=True),
    ],
)