Skip to content

Commit

Permalink
Add support for vibe.web.auth in RPC interfaces.
Browse files Browse the repository at this point in the history
  • Loading branch information
s-ludwig committed Apr 10, 2024
1 parent 0c47934 commit 35b3567
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 7 deletions.
6 changes: 3 additions & 3 deletions web/vibe/web/auth.d
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ struct Role {
static @property R!(Op.ident, name, void, void) opDispatch(string name)() { return R!(Op.ident, name, void, void).init; }
}

package auto handleAuthentication(alias fun, C)(C c, HTTPServerRequest req, HTTPServerResponse res)
package auto handleAuthentication(alias fun, C, AUTH_ARGS...)(C c, AUTH_ARGS auth_args)
{
import std.traits : MemberFunctionsTuple;

Expand All @@ -161,9 +161,9 @@ package auto handleAuthentication(alias fun, C)(C c, HTTPServerRequest req, HTTP
} else {
static assert(!is(AR == void), "Missing @auth(...)/@anyAuth attribute for method "~funname~".");

static if (!__traits(compiles, () @safe { c.authenticate(req, res); } ()))
static if (!__traits(compiles, () @safe { c.authenticate(auth_args); } ()))
pragma(msg, "Non-@safe .authenticate() methods are deprecated - annotate "~C.stringof~".authenticate() with @safe or @trusted.");
return () @trusted { return c.authenticate(req, res); } ();
return () @trusted { return c.authenticate(auth_args); } ();
}
} else {
// make sure that there are no @auth/@noAuth annotations for non-authorizing classes
Expand Down
66 changes: 62 additions & 4 deletions web/vibe/web/rpc.d
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
`ref` and `out` parameters, exceptions, properties returning interfaces,
and properties returning `vibe.web.rest.Collection!I`.
Authorization and authentication is supported via the `vibe.web.auth`
framework. When using it, the `authenticate` method should be defined as
`@noRoute T authenticate(ref const WebRPCPerrInfo)`, where `T` is the type
passed to the `@requiresAuth` UDA.
Any remote function calls can execute concurrently, so that the connection
never gets blocked by an unfinished function call.
Expand Down Expand Up @@ -90,6 +95,7 @@ import vibe.http.server;
import vibe.http.websockets;
import vibe.stream.tls : TLSCertificateInformation;
import vibe.web.internal.rest.common : RestInterface, SubInterfaceType;
import vibe.web.auth;
import vibe.web.common;
import vibe.web.rest : Collection;

Expand Down Expand Up @@ -242,7 +248,7 @@ final class WebRPCPeerImpl(I, RootI, string method_prefix) : I

Bson args = Bson.emptyObject;
foreach (i, pname; ParameterIdentifierTuple!method)
static if (!(ParameterStorageClassTuple!method[i] & ParameterStorageClass.out_))
static if (!is(typeof(args[i]) == AuthInfo!I) && !(ParameterStorageClassTuple!method[i] & ParameterStorageClass.out_))
args[pname] = serializeToBson(params[i]);
auto seq = m_handler.sendCall(method_prefix ~ __traits(identifier, method), args);
auto ret = m_handler.waitForResponse(seq);
Expand Down Expand Up @@ -273,10 +279,26 @@ version (unittest) {
int get(int index);
}

@requiresAuth!TestAuthInfo
private interface TestAuthI {
@safe:
@noAuth void login();
@noAuth int testUnauthenticated();
@auth(Role.authenticatedPeer) int testAuthenticated();
@noRoute TestAuthInfo authenticate(ref const WebRPCPeerInfo peer);
}

struct TestAuthInfo {
bool authenticated;

bool isAuthenticatedPeer() @safe nothrow { return authenticated; }
}

private interface TestI {
@safe:
@property TestSubI sub();
@property Collection!TestCollI items();
@property TestAuthI auth();
int add(int a, int b);
void add2(int a, int b, out int c);
int addmul(ref int a, int b, int c);
Expand All @@ -292,15 +314,32 @@ version (unittest) {
int get(int index) { return index * 2; }
}

private class TestAuthC : TestAuthI {
private bool m_authenticated;

void login() { m_authenticated = true; }
@noAuth int testUnauthenticated() { return 1; }
@auth(Role.authenticatedPeer) int testAuthenticated() { return 2; }

@noRoute
TestAuthInfo authenticate(ref const WebRPCPeerInfo peer)
{
return TestAuthInfo(m_authenticated);
}
}

private class TestC : TestI {
TestSubC m_sub;
TestCollC m_items;
TestAuthC m_auth;
this() {
m_sub = new TestSubC;
m_items = new TestCollC;
m_auth = new TestAuthC;
}
@property TestSubC sub() { return m_sub; }
@property Collection!TestCollI items() { return Collection!TestCollI(m_items); }
@property TestAuthI auth() { return m_auth; }
int add(int a, int b) { return a + b; }
void add2(int a, int b, out int c) { c = a + b; }
int addmul(ref int a, int b, int c) { a += b; return a * c; }
Expand All @@ -310,6 +349,7 @@ version (unittest) {

unittest {
import core.time : seconds;
import std.exception : assertThrown;
import vibe.core.core : setTimer;

auto tm = setTimer(1.seconds, { assert(false, "Test timeout"); });
Expand Down Expand Up @@ -359,6 +399,12 @@ unittest {
foreach (i; 0 .. 4)
assert(cli.items[i].get() == i * 2);

// "auth" framework tests
assert(cli.auth.testUnauthenticated() == 1);
assertThrown(cli.auth.testAuthenticated());
cli.auth.login();
assert(cli.auth.testAuthenticated() == 2);

// make sure the reverse direction got established and tested
while (!got_client) yield();
}
Expand Down Expand Up @@ -672,12 +718,24 @@ private final class WebSocketHandler(I) {
alias outparams = refOutParameterIndices!method;
alias paramnames = ParameterIdentifierTuple!method;

SI impl = resolveImpl!qualified_name(m_impl);

static if (isAuthenticated!(SI, method)) {
typeof(handleAuthentication!method(impl, m_peerInfo)) auth_info;

auth_info = handleAuthentication!method(impl, m_peerInfo);
}

ParameterTypeTuple!method args;
foreach (i, name; paramnames)
static if (!(ParameterStorageClassTuple!method[i] & ParameterStorageClass.out_))
foreach (i, name; paramnames) {
static if (is(typeof(args[i]) == AuthInfo!SI))
args[i] = auth_info;
else static if (!(ParameterStorageClassTuple!method[i] & ParameterStorageClass.out_))
args[i] = deserializeBson!(typeof(args[i]))(arguments[name]);
}

SI impl = resolveImpl!qualified_name(m_impl);
static if (isAuthenticated!(SI, method))
handleAuthorization!(SI, method, args)(auth_info);

alias RT = typeof(__traits(getMember, impl, __traits(identifier, method))(args));
static if (!is(RT == void)) {
Expand Down

0 comments on commit 35b3567

Please sign in to comment.