Is there a way to check if a function from an actor B is called from an actor A ?
In other words, is it possible to check if a shared({caller}) equals the principal of another canister / actor?
import Principal "mo:base/Principal";
actor B {
public shared query({ caller = A }) func say(phrase : Text) : async Text {
let owner : Principal = Principal.fromActor(B);
// Restrict access to this function to a particular actor A or B?
return phrase;
};
};
It’s a bit strange to call this “owner”, self would be more idiomatic.
This code checks if the caller is yourself. If you want to check if the caller is some other canister, you have to compare caller against that… maybe your confusion is less about the comparison or the check, but rather how to get the identity (principal) of that “other” canister in the first place?
Or do you want to recognize all canisters that have the same code?
I have try the following, but I guess then my error is on how to get the identity of the canisters?
// Canister A
import Principal "mo:base/Principal";
import Error "mo:base/Error";
import B "canister:B";
actor A {
public query func say(phrase : Text) : async Text {
let result: Text = await B.say(phrase);
return result;
};
};
// Canister B
actor B {
public shared query({ caller }) func say(phrase : Text) : async Text {
let owner : Principal = Principal.fromActor(B);
if (owner != caller) {
throw Error.reject("No permission");
};
return phrase;
};
};
import Principal "mo:base/Principal";
import Error "mo:base/Error";
import A "canister:A";
actor B {
public shared query({ caller }) func say(phrase : Text) : async Text {
let owner : Principal = Principal.fromActor(A);
if (owner != caller) {
throw Error.reject("No permission");
};
return phrase;
};
};
Just like you do, query({caller = name_of_the_variable_you_want_to_use}) or variants there of. The thing after query is a normal Motoko pattern (like in case), matching on a record that has a field caller
Ah, you get an error message! My bad, I missed that.
Is the other canister a canister in the same dfx project? Then this should work, and maybe some dfx expert can help.
If not, then you can hardcode the principal (actor "xxx-yyy-zzz"), or you can add code to your service to set the ACL (access control list, which is what you are implementing here) later, via update calls.
I can successfuly import the canister to make the call, like MyCanister.say("hello");, but, cannot use it in the Principal.fromActor(MyCanister) function.
I thought about the hard-coded principal but, did not thought about the ACL, good idea .
Nevertheless I was hoping for an easier built-in solution, if there aren’t, sure will have to follow that path.
Sounds interesting but, it is still unclear to me how a class actor can be imported in another actor, another canister, because import MyCanister "canister:MyCanister"; does not work with class actor.
Don’t you happen to have a simple example to share?
One way around this is to create a type that matches the signature of your actors. Then you can reference them by type instead of class. I’m not at a computer at the moment, but can try to write something up tomorrow.
Ultimately I implemented it with a custom solution - i.e. I’ve got an environment modules that contains a list of canister ids and a function that checks if a caller match this list.
// env.mo
module {
public let manager: [Text] = ["aaaaa-ziaaa-aaaai-aaafq-cai"];
}
// utils.mo
import Principal "mo:base/Principal";
import Array "mo:base/Array";
import Env "../env";
module {
public func isManager(caller: Principal): Bool {
hasPrivilege(caller, Env.manager);
};
private func hasPrivilege(caller: Principal, privileges: [Text]): Bool {
func toPrincipal(entry: Text) : Principal {
Principal.fromText(entry);
};
let principals: [Principal] = Array.map(privileges, toPrincipal);
func filterAdmin(admin: Principal): Bool {
admin == caller
};
let admin: ?Principal = Array.find(principals, filterAdmin);
switch (admin) {
case (null) {
return false;
};
case (?admin) {
return true;
}
}
};
}
// used in one of my actor
public shared({ caller }) func doSomething(): async() {
if (not Utils.isManager(caller)) {
throw Error.reject("Unauthorized access. Caller is not a manager.");
};
...
};