GSoC Week2

Date: June 20, 2022 → June 26, 2022

Tasks Overview

  • implement casbin middleware for poem
  • resolve an issue of sqlx-adapter
  • fix casbin-rs benchmark workflow
  • research poem-openapi and discuss on casdoor-rust-sdk

Task 1 Poem-casbin

This week, I plan to write code for poem-casbin.

First, to get familiar with what casbin middleware do and how to implement it, I take a look at existing casbin middlewares.

For example:

https://github.com/casbin-rs/actix-casbin-auth

This repository is casbin-rs access control middleware for actix-web framwork

To figure out what does this code do, I have to first learn how to write middleware for actix. I found an awesome tutorial here:

Demystifying Actix Web Middleware

To implement a middleware, we must implement Transform and Service traits.

Actix’s Service represents anything that takes a request and returns a response, it looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pub trait Service<Req> {
/// Responses given by the service.
type Response;

/// Errors produced by the service when polling readiness or executing call.
type Error;

/// The future response value.
type Future: Future<Output = Result<Self::Response, Self::Error>>;

/// Returns `Ready` when the service is able to process requests.
fn poll_ready(&self, ctx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;

/// Process the request and return the response asynchronously.
fn call(&self, req: Req) -> Self::Future;
}

We’ll mainly work on call() function, and this is the main part of axtix-casbin-auth.

The Transform implementation’s only job is to create new middleware instances that wrap other services, it looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pub trait Transform<S, Req> {
/// Responses produced by the service.
type Response;

/// Errors produced by the service.
type Error;

/// The `TransformService` value created by this factory
type Transform: Service<Req, Response = Self::Response, Error = Self::Error>;

/// Errors produced while building a transform service.
type InitError;

/// The future response value.
type Future: Future<Output = Result<Self::Transform, Self::InitError>>;

/// Creates and returns a new Transform component, asynchronously
fn new_transform(&self, service: S) -> Self::Future;
}

Here we have to implement new_transform() function to wrap casbin enforcer and actix service together, like:

1
2
3
4
5
6
fn new_transform(&self, service: S) -> Self::Future {
ok(CasbinMiddleware {
enforcer: self.enforcer.clone(),
service: Rc::new(RefCell::new(service)),
})
}

Note this middleware only takes care of authorization, so user should put actix_casbin_auth::CasbinVals which contains subject(username) and domain(optional) into extension before calling this middleware.

1
2
3
4
5
let vals = CasbinVals {
subject: String::from("alice"),
domain: None,
};
req.extensions_mut().insert(vals);

Now I focus on call() function of Service trait:

First, it gets path and action (method) of request, then gets CasbinVals from extension.

1
let option_vals = req.extensions().get::<CasbinVals>().map(|x| x.to_owned());

Then he calls enforce_mut() to authorize this request and returns response depending enforce result.

Also, I take a look at tests to see how to use this middleware, I found it quite easy to use, just wrap endpoint in middlwares like:

1
2
3
4
5
6
7
let mut app = test::init_service(
App::new()
.wrap(casbin_middleware.clone())
.wrap(FakeAuth)
.route("/pen/1", web::get().to(|| HttpResponse::Ok()))
.route("/book/{id}", web::get().to(|| HttpResponse::Ok())),
)

After learning actix-casbin-auth I’m clear about what work casbin middleware does. So I turned to learn how to write poem middleware.

There are not many tutorial on implementing middleware for poem, but poem provides bunch of middleware examples in https://github.com/poem-web/poem/tree/master/examples/poem . So I took a look at these examples and figured out how to implement middleware for poem.

Just like actix, we have to implement two traits: Middleware and Endpoint.

Middleware is like Transform in actix, it wraps other services, it looks like:

1
2
3
4
5
6
7
pub traitMiddleware<E:Endpoint> {
/// New endpoint type.
type Output:Endpoint;

/// Transform the input[`Endpoint`] to another one.
fn transform(&self, ep: E) -> Self::Output;
}

Endpoint is like Service in actix, it takes a request and returns a response, it looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pub trait Endpoint: Send + Sync {
/// Represents the response of the endpoint.
type Output: IntoResponse;

/// Get the response to the request.
async fn call(&self, req: Request) -> Result<Self::Output>;

/// Get the response to the request and return a [`Response`
async fn get_response(&self, req: Request) -> Response {
self.call(req)
.await
.map(IntoResponse::into_response)
.unwrap_or_else(|err| err.into_response())
}
}

Now I know how to write middleware in poem, so I start implementing casbin middleware for it, this is my first PR:

https://github.com/casbin-rs/poem-casbin/pull/1

Task 2 Resolve sqlx-adapter issue

https://github.com/casbin-rs/sqlx-adapter/issues/65

This issue has some update, one user complains that why DATABASE_URL must be presented instead of to be optional.

It’s because sqlx-adapter uses query! marco to statically check SQL queries.

According to docs of sqlx, to use query!:

  • The DATABASE_URL environment variable must be set at build-time to point to a database server with the schema that the query string will be checked against. All variants of query!() use dotenv so this can be in a .env file instead.
  • Or, sqlx-data.json must exist at the workspace root.

To use sqlx-data.json , you can follow the docs for offline mode.

query! can be configured to not require a live database connection for compilation, but it requires a couple extra steps:

  • Run cargo install sqlx-cli.
  • In your project with DATABASE_URL set (or in a .env file) and the database server running, run cargo sqlx prepare.
  • Check the generated sqlx-data.json file into version control.
  • Don’t have DATABASE_URL set during compilation.

Your project can now be built without a database connection (you must omit DATABASE_URL or else it will still try to connect). To update the generated file simply run cargo sqlx prepare again.

Note: As sqlx-adapter has generated sqlx-data.json for postgres, so when using postgres you don’t need provide DATABASE_URL or sqlx-data.json . But for mysql and sqlite, you must provide DATABASE_URL or sqlx-data.json

However, it would be better if we can generate sqlx-data.json for all three kinds of database in sqlx-adapter , so users won’t bother setting DATABASE_URL or generating sqlx-data.json by themselves. I’ve been tried a while but found cargo sqlx prepare would just overwrite sqlx-data.json.

I’ve been reading related issues like:

https://github.com/launchbadge/sqlx/issues/1223

https://github.com/launchbadge/sqlx/issues/121

but found them not very helpful, so I open an issue at sqlx:

https://github.com/launchbadge/sqlx/issues/1927

No one reply to me though.

Task 3 casbin-rs benchmark workflow

Last week I found benchmark workflow doesn’t work well that it fails to post comment to PR.

I’ve searching a while for this issue and reading some related discussions/issues:

https://github.com/actions/first-interaction/issues/10

GitHub actions are severely limited on PRs

Automatic token authentication - GitHub Docs

“Resource not accessible by integration” for adding a comment to a PR via action

The reason is GITHUB_TOKEN only has read permission to pull requests when access by forked repos, so when a new PR is coming, running workflows only has read-only permission (of course not allowed to post comment), that’s why we get 403 here: https://github.com/casbin/casbin-rs/runs/6409294114#step:5:459

Some users have created actions that can post comment on PR, like:

https://github.com/mshick/add-pr-comment

https://github.com/nyurik/auto_pr_comments_from_forks

But it’s difficult to combine them with boa-dev/criterion-compare-action.

GitHub has introduced a new event type: pull_request_target, which allows to run workflows from base branch and pass a token with write permission.

GitHub Actions improvements for fork and pull request workflows | The GitHub Blog

It says:

The event runs against the workflow and code from the base of the pull request. This means the workflow is running from a trusted source and is given access to a read/write token as well as secrets enabling the maintainer to safely comment on or label a pull request.

I give this a try.

I first created a PR from a fork repo:

Untitled

It fails as before.

Then I modify pull_request.yml to use pull_request_target , then create another PR.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
on:
pull_request_target:
branches: [ master ]
name: Benchmarks
jobs:
runBenchmark:
name: run benchmark
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
# ...

Now it works well:

Untitled

I made a PR and it got merged.

https://github.com/casbin/casbin-rs/pull/298

Task 4 casdoor-rust-sdk

This week I take a look at poem as mentor suggested.

Poem has a sub crate poem-openapi

poem/poem-openapi at master · poem-web/poem

However, after I digging it a while, I found this is for writing openapi service, not client.

Here is example it provides:

1
2
3
4
5
6
7
8
9
10
#[OpenApi]
impl Api {
#[oai(path = "/hello", method = "get")]
async fn index(&self, name: Query<Option<String>>) -> PlainText<String> {
match name.0 {
Some(name) => PlainText(format!("hello, {}!", name)),
None => PlainText("hello!".to_string()),
}
}
}

Last week another guy was assigned to this project too, so this week I discussed with him, as he said he has written ~100 lines of code, I think it’s better that he made an initial PR to upload SDK framework, then we can make PRs to complete it .

https://github.com/casdoor/casdoor-rust-sdk/issues/1

However, not long after I replied under this issue, I found I’m blocked by Casdoor organization :(

I can’t post comment in issues/PRs of any repo of casdoor org, also can’t fork any repo belonging to casdoor org:
comment
fork

I don’t understand why I’m blocked and I’ll discuss with my mentor tonight.