Securely passing callbacks from client side code

Securely passing callbacks from client side code

by Dan Poltawski -
Number of replies: 33

(I'm in the unusual situation of posting about code which i'm reviewing rather than code i'm writing, so bear that in mind)

There are situations which are starting to come up where we want to provide some reusable client components in core and tend to involve a generic bit of reusable client side code and some sort of server side callback implemented by a component. A nice example is Marina's reusable element for inline name editing (MDL-51802), there is another in MDL-52715.

In these situations we allow client side code to provide a callback to be executed server side. This makes me quite cautious from a security POV - I don't think we have many situations where we expected a server side functions/classes to be executed from potentially user-changable input. I am cautious about it (perhaps too much).

Do we have existing coding styles for dealing with this situation? The closest example I can think of is the comments api - (which wasn't itself a shining example of a safe implementing) and I think it was controlled by naming. In MDL-51802, Marina has ensured that the class implements a subclass. 

It would be good to have an established pattern for achieving this, thoughts welcome (even just to say that sounds fine)!

Average of ratings: Useful (3)
In reply to Dan Poltawski

Re: Securely passing callbacks from client side code

by David Monllaó -

Hi,

Thanks for raising the topic Dan, we talked briefly about this in HQ with Adrian and Marina, same conclusion, no standard way to do it. I would prefer interfaces over subclasses.

In reply to David Monllaó

Re: Securely passing callbacks from client side code

by Dan Poltawski -
I would prefer interfaces over subclasses.

I agree. Though, do we need to go that far? Is your naming convention strong enough protection? 


(Off topic: I have recently been drinking the Kool-Aid of protocol orientated programming in swift (original talk) which over-influences me against inherence, it seems trendier wink)

In reply to Dan Poltawski

Re: Securely passing callbacks from client side code

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

(Sorry, this post is a bit stream-of-conciousness, but I think there might be something of use in there.)


Perhaps an even more archetypal example is file downloads. In this case, the 'callback' is just a HTTP get request with a particular URL.

How would one describe what the File API to verify the file can be served?

The request goes to a known script (pluginfile.php), then there are naming convention for how that is dispatched to plugin. Which file is being requested is defined by some common concepts, like contextid, file area, instance id, etc.

I don't know if that is a useful example. How would one abstract that?

Single callback-point in the plugin (e.g. .../classes/output/rename_thing_callback_handler). Specific thing being renamed handled by variables $area and $itemid.


I see. Looking at the code, we acutally have the specific thing being handled by passing around the class name. E.g. a class called core_course\output\sectionname, and it is this passing a class name that is triggering your unease.

I think I share your concern.

(Also, why does that class name not follow the naming conventions. Surely it should be section_name, and the base class editable_title. However, those are poor names.)


Oh, no. I was not understanding the code. (Suggests we need better comments to explain what is going on). In fact, core_course\output\sectionname is a sort of active-record pattern - so if you want to use this generically-reusable widget, you have to implement the active record patter for the thing being edited, whether that makes sense or not, and we don't generally use active record in Moodle. Hmm. I am sure we could define a more generic callback API here.


So, which bit are you concerned about Dan?  Is it https://github.com/moodle/moodle/compare/master...marinaglancy:wip-MDL-51802-master#diff-50a157a9f2830b929f9d37976809792bR42. Isn't that just how the generic ajax.call works? Ah! No, it is the https://github.com/moodle/moodle/compare/master...marinaglancy:wip-MDL-51802-master#diff-50a157a9f2830b929f9d37976809792bR44 line.

I think the answer is that you could rename the configuration type from 'callback' (which is an implementation detail) to 'itemtype', or something like that, which is what it is. Well, actually, I would split it to 'component' and 'itemtype'. Then, the fact that this leads to the class name /{component}/output/{itemtype} is a simple convention.

In reply to Tim Hunt

Re: Securely passing callbacks from client side code

by Dan Poltawski -
I think the answer is that you could rename the configuration type from 'callback'

That isn't really the part which concerns me, there is a different alternative implementation with straighforward callback naming convention here from Adrian:

https://github.com/abgreeve/moodle/compare/4ae6455...wip-MDL-52715-master-additional#diff-de2e2038bbd338453ffcba537ef524f9R328

But is that enough?

Note, part of me thinks i'm being overcautious about this, I am sure there are similar situations possible with get params which are only protected by naming conventions.

In reply to Dan Poltawski

Re: Securely passing callbacks from client side code

by David Monllaó -

The only reason why the function name is not hardcoded is because we might have more than one of these callbacks for each component, we could expand the current 'fragment_' hardcoded part to something like 'fragment_callback_xxxx_yyyy' to prevent conflicts or security issues if any 3rd party plugin already implements a mod_plugin_fragment_delete(), https://github.com/abgreeve/moodle/compare/4ae6455...wip-MDL-52715-master-additional#diff-de2e2038bbd338453ffcba537ef524f9R274 is already PARAM_COMPONENT, I can not think of much more problems than the naming conflict.

In reply to David Monllaó

Re: Securely passing callbacks from client side code

by Marina Glancy -
Picture of Core developers Picture of Moodle HQ Picture of Moodle Workplace team Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

I know that callbacks are currently much more common in moodle than classes implementing interfaces (or extending parent classes).

I would personally prefer to move away from callbacks in lib.php towards autoloaded classes but I will not insist on anything. As soon as integrators/devcommunity makes a decision, I'm happy to change my code to whatever is decided.

In reply to Marina Glancy

Re: Securely passing callbacks from client side code

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

I completely agree that the API implemented by the plugin should be packaged up as an autoloaded class, not in lib.php.

However, that does not force you to use an active-record-like API, where on instance of the class represents one renameable thing. You could have a a single class that is a rename_handler for that plugin, e.g.

class \mod_quiz\rename_handler extends \core\rename_handler_base {
    public function rename($itemtype, $instanceid, $newname) {}
}

That is not a specific API suggestion, nor am I saying this would definitely be better. I am just trying to make it clear what sort of alternate API might be worth considering.

In reply to Marina Glancy

Re: Securely passing callbacks from client side code

by Dan Poltawski -

Please don't misunderstand my message that I prefer the 'named callback' approach, I don't, i'm just putting on the table the different approaches used. I do agree with Tim that it'd be better to avoid active-record-like API (I didn't until he pointed it out wink).

The more I think about it, the more I think that interfaces are the correct solution to this (clue is in the name!) - we describe the contract in code and get as close as possible to type checking as we can in php without putting too many restrictions on consumers.. I can't think of a more robust way to do it and I think its not overly onerous on implementers.

To move us forward, I'd like to propose a rule. It's hard to come up with the scope of what you'd apply this rule to as we've many existing named callbacks (and so on) and I can't think of a way to succinctly describe it.. but I'd like to put on the table that these callbacks:

  • Must be described by an interface
  • Must be type checked ( class instanceof interface ) at the point of use

Is it reasonable?

Average of ratings: Useful (1)
In reply to Dan Poltawski

Re: Securely passing callbacks from client side code

by Marina Glancy -
Picture of Core developers Picture of Moodle HQ Picture of Moodle Workplace team Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers Picture of Testers
In my branch for MDL-51802 the class is used for both rendering the name when preparing the page AND updating the element, see
https://github.com/moodle/moodle/compare/master...marinaglancy:wip-MDL-51802-master#diff-68e38afda0cb8981c4b1c13bf730e4b5R84

so ($itemtype, $instanceid) should be arguments of constructor and not "rename" function.

Dan, I can change it to the interface instead of the extending parent class but it's not quite clear for me where should I move all the logic that is present in \core\output\editabletitle atm
In reply to Marina Glancy

Re: Securely passing callbacks from client side code

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

"so ($itemtype, $instanceid) should be arguments of constructor and not "rename" function."

That is exactly the API design question that Dan and I want to discuss.

In reply to Marina Glancy

Re: Securely passing callbacks from client side code

by Dan Poltawski -

(I was trying to keep it on the broader point rather than the details of any specific issue heh smile)

I guess I was thinking of something like a delegate and so you'd turn the callback into a delegate, so you'd make something like:

public abstract function update_title($newtitle, $itemtype, $instanceid);
public abstract function render_title($title, $itemtype, $instanceid);

And then it gives the flexibility of adding this functionality on existing classes (e.g. format_topics instead of format_topics\output\sectioname) - that might be particularly advantageous where you need to do complex localised permission checkling.

Average of ratings: Useful (1)
In reply to Dan Poltawski

Re: Securely passing callbacks from client side code

by Marina Glancy -
Picture of Core developers Picture of Moodle HQ Picture of Moodle Workplace team Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

I tried to implement this suggestion but I found that render_title does not really need to be a part of the interface. All we need in the webservice is the update method. How component renders the title is its own business. I still had to implement templatable class, so there are two now - one interface and one templateable class. Abstract function update_title is supposed to return instance of this templateable class.

So we basically ended up with an interface with a single function - it could just as well be a callback then. However class nature could be helpful in cases such as section name - I define abstract parent class in core and each course format defines the child class with very few code repetition.

I attached the new branch to my issue but I don't like it myself - it is more difficult to follow. Also constructor becomes quite useless and function update_title in fact works as if it was static.

In reply to Marina Glancy

Re: Securely passing callbacks from client side code

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Although there is just a single function, a class is still better than a callback (I think). Not only, as you say, can you have a base class reduce duplicated. Also, we can use class auto-loading to find it.

I think it is not a problem if the constructor does not do anything. Generally, constructors are very inflexible. A call to a constructor must return a new object (so it cannot reuse something existing) and it must be a new thing of exactly that class, where with a normal function return, it can be anything that extends/implements the given class/interface. The constructors of your editabletitle classes take no arguments, which seems like a good thing. I wonder if it works to make that a general rule for this sort of call-back class?

One small point is that this bit of the code would be more elegant with a helper function, so you could just say

$obj = core_component::get_component_callback($component, 'editabletitle', 'core\\editabletitle');

I think the main reason the code is currently hard to follow is that we have not yet found the right names for things. For example the class receives the callback when the value is edited is called 'editabletitle', but it is not a type of title. OK, so I should not just criticise, I should suggest something better.

First, I want to get away from 'title', because in the quiz, this same UI is used to edit the mark for each question. So, if this is not about editing titles, what is it? I guess the common feature is that these are things that can be edited in-place, and the point is to use a common UI for people to do that. This is good both from a code point of view (less duplicate code) and also from a UI point of view: more consistency. So, I think that the common factor here is that this is a re-usable UI widget, or element, as I suppose we call them in Moodle, since we are aiming to have a UI element library.

So, I guess I would call the UI widget inplace_editable_value (instead of editabletitle - I really don't get why you don't have underscores in your class names?)

So, so that is the UI element (templateable subclass). What do we call the callback class? Well, the purpose the callback is to save the edited value (then return the new value ready for display). Therefore, I would call it inplace_editable_value_saver.

So, with that naming, your externallib code becomes something like this:

    public static function update_inplace_edited_value(
                $component, $itemtype, $value, $instanceid) {
        global $PAGE;
        // Validate and normalize parameters.
        $params = self::validate_parameters(self::update_inplace_edited_value_parameters(),
                array('component' => $component, 'itemtype' => $itemtype,
                        'value' => $value, 'instanceid' => $instanceid));

        $saver = core_component::get_callback($params['component'],
                'inplace_edited_value_saver', 'core\\inplace_edited_value_saver');
        $updatedalue = $saver->update_value($params['value'], $params['itemtype'],
                $params['instanceid']);

        return $updatedalue->export_for_template($PAGE->get_renderer('core'));
    }


Also, while you say "How component renders the title is its own business", that is probably only partly true. Since these are headings, the way the heading looks (font size, style and colour) may vary. But at the same time this is meant to be a reusable UI widget, so the way the edit icon looks, and how when you press it a text entry box appears, etc. should all be as consistent as possible. So, I wonder how much control components need over the display?

Sorry, this got a bit long and rambling. I hope at least some of it is helpful.

http://dieswaytoofast.blogspot.co.uk/2015/10/two-hard-things-in-computer-science.html

In reply to Tim Hunt

Re: Securely passing callbacks from client side code

by Marina Glancy -
Picture of Core developers Picture of Moodle HQ Picture of Moodle Workplace team Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

Thanks Tim, it is indeed very helpful.

I only called it editabletitle because I re-used the UI of the activity name editing and it had CSS class editing_title. I do not mind renaming it nor inserting underscores.

Do you suggest "inplace_editable_value" or "inplace_edited_value"? I think you mistyped somewhere.

So, we have:

core\output\inplace_editable_value - templateable element

core\inplace_editable_value_saver - interface that has a single function update_value($itemtype, $instanceid, $newvalue) that must return the templateable element above

yourcomponent\inplace_editable_value_saver - class in the component or plugin that implements the interface above

core_update_inplace_editable_value - web service

core/inplace_editable_value - AMD module


Correct?

Are you sure we can not drop "inplace_" from these names?

Also I noticed that with this naming we will not be able to create the class implementing interface in core. We either have to acknowledge this limitation or should consider moving interface somewhere else


Re your other questions - editable values will not only be headings - they can be links, names, cells in a table, they can be anywhere. If they are headings, the template is rendered inside the <h2> element so it does not matter for the template where exactly it will be used. No tags are expected in "displayvalue" except for maybe a link (for example on Manage tags page). When preparing the value for display the component needs to: apply format_string(), wrap in a link if needed, check if current user is actually able to edit the title or not.

In reply to Marina Glancy

Re: Securely passing callbacks from client side code

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Just to be clear, I am not claiming that inplace_editable_value is a good name. Just that it was the best one I have been able to think of so far. I don't particularly like it, and I would not be surprised if I typed it inconsistently. If you manage to think of something better, I would be the first to cheer.

However, when trying to think of a name, it seemed to me the thing that made this UI element unique is that you edit the value in-place, where it normally appears. Hence the name. If I was going to delete any word from the name, I would get rid of 'value' since that semantically almost void.

The next uncertainly I have is whether inplace_editable_value_saver is a generic reusable thing, in which case should be in the core namespace where it is now. Or, is it something specific to this UI element, in which case it should be in the core\output namespaces (or a sub-namespace?)

Given that you can't have '-' in class names, I don't know if inplace or in_place is better in these names.

You could make a class with a default implementation of the interface. You would just have to call it something like class inplace_editable_value_saver_base implements inplace_editable_value_saver.

With the name of the AMD module, do we have a convention to call the AMD module the same as the element? If not, you could call the JavaScript code edit_value_in_place, since the JavaScript does something (verb) rather than representing it (noun) like the templateable does.

In reply to Tim Hunt

Re: Securely passing callbacks from client side code

by Marina Glancy -
Picture of Core developers Picture of Moodle HQ Picture of Moodle Workplace team Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

Yes, let's drop "_value", sounds good. There is no convention about AMD module/template naming because afaik this is going to be the first such bundle in moodle. I would vote for keeping the same name even though English-wise it is not accurate - this will greatly increase code readability later.

At the same time I just thought that I should not limit this element to be text-input only. At least we can immediately support <select> and later autocomplete element, date/time chooser and, hopefully, eventually htmleditor.

In reply to Marina Glancy

Re: Securely passing callbacks from client side code

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

To me your second paragraph raises interesting questions. Would all the editable things be the same, or would they be different things that share some of the same code?

I mean, perhaps what you are building now is in_place_editable_text, and later we might add in_place_editable_select, in_place_editable_autocomplete, etc. (That would be like the similar range of admin settings classes.)

If so, I think for now that you should just build in_place_editable_text and make it work as simply as possible. Then later, if we add more, we can refactor. (Basically, I am just reminding you of the YAGNI principle.)


In reply to Tim Hunt

Re: Securely passing callbacks from client side code

by Marina Glancy -
Picture of Core developers Picture of Moodle HQ Picture of Moodle Workplace team Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

yagni is a good motto but we will need [at least] selects straight away. Look at jira (tracker) UI - all fields are editable in-place. We really need to move in the same direction for moodle.

If I move away the generation of <input> element from template to the AMD module, all element-type logic will be self-contained there. Default element type is text input, of course. In fact, I already did it in my branch

Average of ratings: Useful (1)
In reply to Dan Poltawski

Re: Securely passing callbacks from client side code

by Damyon Wiese -
I think that some of this discussion has gone off topic into how to solve the one specific issue listed in the tracker. IMO this is not a good way to create an API. Are we going to start implementing things with interfaces and come up with something else from the hooks discussion?

If we need a new way to call a callback - lets start by listing all the requirements etc before we come up with a design. This is related to the hooks discussion.

Some requirements from the top of my head are:

1. Do not enforce naming conventions for classes. Allow plugin authors to use namespaces as they see fit (as long as it confirms to the coding style for namespaces).
2. Make it fast to check - tell me all the plugins implementing this callback, tell me all the callbacks implemented by this plugin (fast to check in php, and fast for a dev to find in the code).
3. Do use interfaces/abstract base classes to define the "contract" for someone implementing an API.
4. It would be nice to have some sort of backwards compatibility with component_callback instead of inventing an entirely new thing.

In reply to Damyon Wiese

Re: Securely passing callbacks from client side code

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

LOL @ 4). That from the person who, when asked to implement and Element library to make it easier for people to style Moodle, instead made a whole new templating layer wink


3) I agree that it would be good to keep some sort of backwards compatibility with component_callback(). Though again, if we were doing this today, it would not be a random function in moodlelib.php. Instead it would probably methods in the core_component class, along with a number of the other surrounding methods in that part of Moodlelib.

In fact, we can make it backwards compatible. Extend the $function argument so that it can be an array ($class, $method) and then Moodle puts the component namespace on the start of the classname, and does not change the method name.

2) Just requires extending the other similar moodlelib.php methods (e.g. get_plugins_with_function or a similar get_components_with_class.


1) I disagree. In the past in Moodle we have always used fixed names for each callback. E.g. forum_add_instance, forum_extend_settings_navigation. This is good. It makes the code easier to understand, and it is easier to grep the code to find all the places a particular callback in implemented.

If you don't use a predictable name for these classes then you just need another callback, or configuration method, to get the name of the function that implements a particular callback. That just seems like unnecessary spaghetti.



Average of ratings: Useful (1)
In reply to Tim Hunt

Re: Securely passing callbacks from client side code

by Marina Glancy -
Picture of Core developers Picture of Moodle HQ Picture of Moodle Workplace team Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

We have four discussion places now (two forum posts and two issues). Cross-posting here my suggestion:

https://moodle.org/mod/forum/discuss.php?d=327349#p1316752

In reply to Marina Glancy

Re: Securely passing callbacks from client side code

by Dan Poltawski -

While there are clear parallels between these two discussions, I want to make clear my original intent of this thread was to discuss the securing of user-changable (class/function/callback)name parameters from client side code (hence title).

In reply to Dan Poltawski

Re: Securely passing callbacks from client side code

by Frédéric Massart ⭐ -
Picture of Core developers Picture of Plugin developers Picture of Testers

Hi guys,

here is my share of thoughts about the callbacks. I am not going to comment at all on the editable-inline feature, more using it as an example at time.

Firstly, I think it’s important to draw a line between callbacks and hooks, they are not the same thing. My rough definition of both would be:

Callback: A callable that is generating a result from a trigger. In Moodle code, the core will often sit in the middle just to be the link between the trigger and the result. There is never more than one callback called at once, and never less than one. When the callback does not exist, the code can not run.

Hook: An optional set of callables. The code will execute just fine when there are no hooks, and if there are there can be more than one. The hooks are extending the functionality of the code that implements them.

Following Damyon’s foot steps, here are the requirements for callbacks that I can think of:

  • Flexible, developers can organise their code however they want. (As Damyon #1)
  • Fast, identifying the callback. (Damyon #2)
  • Easy to implement, in core.
  • Easy to use, well documented.
  • Secure.

I don’t really mind backwards compatibility to be broken, we need to break things to make them better. Also we can’t make things faster if we have to follow two paths (new and old). As long as there is a clear justification and simple migration process it should be fine to not be backwards compatible.

Why I don’t like classes and interfaces?

I really do not think callbacks should be defined by interfaces or abstract classes. Interfaces are particularly bad because:

  • They can not never be changed, or will heavily break backwards compatibility
  • The risk of method names clashes increases (I want to use the interfaces on any existing class)
  • Using the interfaces on an existing class can not work, only I know how to instantiate my object
  • If the interface defines static methods, then classes are just placeholders for functions
  • If I properly use the interfaces (e.g. mod_assign implements callback), then ‘$class instanceof interface’ is not very secure any more.

Classes are not better because you still need to create a class, and you can not have more than one callback per class. And just as interfaces they are just placeholders for static methods, or they can be instantiated but that probably has no value.

More importantly, I find that both concepts encourage developers to duplicate their code, thus reducing maintainability and security. What I am trying to say is that the callback callbable should only ever receive arguments, forward those to the main ‘controller’, then return the result. A class or an interface to do that is a serious overkill. And encouraging splitting the code in different places is a serious problem too.

Also, if we take the example of editable-inline thingy, why don’t we implement the methods ‘can_edit()’, and ‘validate_param()’. All the plugins will have to check the permissions, and validate the parameters, and maybe do other things as well… So, we add those to the interface/class and now clearly all the logic that has already been implemented elsewhere has to be duplicated in the callback class.

I vote against interfaces and subclasses.

So what do we do?

We need callbacks to be flexible, so developers should be able to place them wherever they want. But it needs to be secure, we can not really accept a callable name to be passed around by the client. We also need good documentation, which means that it should be easy for the developer to find out what a callback receives, and what it should return. And it needs to be blazing fast, we don’t want to start loading an entire PHP file to check if a single function exists.

Let’s store the callbacks that a plugin defines in a file, db/callbacks.php. The plugin developers will have to state the name of their callback, what they are meant to be used with, and the callable to use (let’s assume it is autoloaded). Here is an example:

$callbacks = array(
    'mod_plugin:core_do_that' => array(
        '\mod\plugin\my_favourite_class::my_do_that',
        'accepts' => 'core_do_that'
    );
)

In core, we would have something like this:

$callbackname = 'mod_plugin:core_do_that';  // Provided by client, insecure.
$callbackargs = array();
$callbackinfo = get_callback_info($callbackname);
$callable = $callbackinfo->callable;
if ($callbackinfo->accepts != 'core_do_that' || empty($callable) || !is_callable($callable)) {
    // Abort!
    die();
}
call_user_func_array($callable, $callbackargs);

With that, we probably have a good security as ‘mod_plugin:core_do_that’ defines what callback it can be used with (accepts), and we can not substitute it with a random callback or callable. It’s also simple because my callable can be defined anywhere, and it is fast because the list of callables will be cached somewhere.

We still have a problem though, how do we document the callback mechanism, and its arguments? We could force ourselves to write documentation, but let’s be honest, that never works. An other option is to create one class, but this time the hard work is on the core developer, not on the part of the user of the callback.

I have not thought about this extensively, but here is an example:

namespace core\callback;

abstract base {

    public __construct($args) {
        foreach ($args as $key => $value) {
            if (developer mode) {
                // Reflection to confirm the property.
            }

            $this->$key = $value;
        }
    }

}

/**
 * Callback part of the API to do_that.
 *
 * Core will forward this to your plugin when...
 */
class do_that extends base {
    
    /** The name of blah... */
    public $name;
    /** The title that will... */
    public $title;
    /** What the user entered as description... */
    public $description;

}

And if I update the core example above, we would have this:

$callbackname = 'mod_plugin:core_do_that';  // Provided by client, insecure.
$callbackinfo = get_callback_info($callbackname);
$callable = $callbackinfo->callable;
if ($callbackinfo->accepts != 'core_do_that' || empty($callable) || !is_callable($callable)) {
    // Abort!
    die();
}
$callbackarg = new \core\callback\do_that(array(
    'name' => 'A name',
    'title' => 'A title',
    'description' => 'A description'
));
call_user_func($callable, $callbackarg);

Potentially, we could have also have something like:

$callback = new \core\callback\do_that(array(
    'name' => 'A name',
    'title' => 'A title',
    'descriptuon' => 'A description'
));
$callbackname = 'mod_plugin:core_do_that';  // Provided by client, insecure.
$callback->call($callbackname);             // Validates the callbackname.

This looks very similar to events, but has a different purpose and is a lot less complex. It’s also, like the observers, very gentle with developers who implement the callables.

I realise that callbacks should have a specific and limited purpose, if not they would probably be better as whole new API, or as hooks, or who knows. E.g. if you want to find all the plugins that accept a certain type of callback you are probably thinking of hooks instead.

Why am I wrong?

After writing all this I tried to apply (in my head) the solution to two examples, the inline-editing, and the javascript forms. In the case of inline-editing my example works fine, but not so much for forms. Simply, the form requires an arbitrary list of arguments that will differ depending on the callback. That is very interesting, callbacks can receive very different arguments even within the same API.

So, I think I was wrong to suggest the \core\callback\base class. It will only work when the API enforces the arguments that a callback is supposed to receive.

To summarise, I would:

  • Define the callbacks in a callback file
  • Attach a ‘type’ name to the callback trigger
  • Check the ‘type’ of the callback callable prior to running it
  • Document how the API works, just like any other API, without the need for a new class

That’s it, that’s my opinion for the day. It’s not unlikely that I will disagree with myself next week.

Thanks for reading!

Fred


In reply to Frédéric Massart ⭐

Re: Securely passing callbacks from client side code

by Tim Hunt -
Picture of Core developers Picture of Documentation writers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

It is a good point that "They can not never be changed, or will heavily break backwards compatibility".

It is not that bad: things will only break (for plugin developers) at the point when you upgrade to a new Moodle version, and when it does it will break with a very clear error message.

But, it is still annoying because it makes it harder to have one version of your plugin code that works with several Moodle versions.

On the other hand, within a single version of Moodle, the interface is really good: It clearly documents which set of related methods you need to implement to make this callback work, and there can be PHP docs telling you exactly how each callback method is supposed to implement.

So, now I am really torn about which is better.

Also, I wonder if this is also purely a theoretical concern. After all we have many APIs defined by base classes (filter_, qtype_, quizaccess_, course format_). Those all evolve over time, and the backwards_compatible breakage does not seem very severe.


(Also, surely with method names, name collisions are less likely, compared to just putting all the callbacks as functions in lib.php.)

In reply to Tim Hunt

Re: Securely passing callbacks from client side code

by Frédéric Massart ⭐ -
Picture of Core developers Picture of Plugin developers Picture of Testers

Thanks Tim,

I think the whole point of my post was to use neither of them ;). And yes, collisions are less likely in a lib.php if the functions are properly prefixed with the component name.

In reply to Frédéric Massart ⭐

Re: Securely passing callbacks from client side code

by Dan Poltawski -

I'm not especially convinced by your arguments against interfaces, especially as I think of my other 'programming experiences' which rely more heavily on interfaces.

If I properly use the interfaces (e.g. mod_assign implements callback), then ‘$class instanceof interface’ is not very secure any more.

Can you explain what you mean by that? 

In reply to Dan Poltawski

Re: Securely passing callbacks from client side code

by Frédéric Massart ⭐ -
Picture of Core developers Picture of Plugin developers Picture of Testers

Predominantly I'm arguing against classes, and therefore interfaces. I do like interface, but not in this scenario. The interface here requires a placeholder class that implements one (or two, three...) interface. But it needs to be a bare class as the constructor parameters are unknown, and therefore need to be empty. So the only thing you end up with is a class that implements interfaces, in other words a file with methods, in other words something very similar to lib.php

"If I properly use the interfaces (e.g. mod_assign implements callback), then ‘$class instanceof interface’ is not very secure any more."

I meant that if you're only validating the class interface, you're not validating that the method called was part of that interface. It does when you only expect the class name as Callback argument. Explaining this makes me realise that this is how you would use interfaces in callbacks anyway, providing that the interface defines the method to call, and is not just an interface like renderable which defines nothing.
In reply to Frédéric Massart ⭐

Re: Securely passing callbacks from client side code

by Cameron 👨‍🦲🟥⚡️ -
Picture of Core developers Picture of Peer reviewers Picture of Testers

Inclined to agree about there being little to no value in using classes for callbacks. A callback (as defined here) does not seem (to me) to require any internal state. Actually in general I think that would be true. The idea of using a method from a class as a callback doesn't make too much sense, because you need to also provide the instance to call it on. So really it has to be static (or just a regular function) to make any sense.

When you realise that, interfaces don't make sense for callbacks either, since an interface is supposed to define instance methods.

Kind of related: In C++ callback functions (as in, invoking a function pointer from another function) must be defined as static if they are part of an object. If that's worth anything to anyone.

Also I think this way of using "callbacks" is kind of weird. Generally a callback is supposed to be provided by the original caller. If I understand this scenario correctly, the original caller can just provide the name of some function which is already defined. I don't know how I feel about that yet.

In reply to Dan Poltawski

Re: Securely passing callbacks from client side code

by sam marshall -
Picture of Core developers Picture of Peer reviewers Picture of Plugin developers

Nobody's talked much about the security aspects. In addition to whatever else is discussed, could security be improved by signing it server-side? In other words let's say you want to make a callback request to a particular function/class/interface/whatever, the server can give a token for allowed options, which can be set up as a param in the require_amd call?

So for instance

callback=\whatever\my_class_name&callbackhash=ab123&sesskey=12345

Where the server creates a hash of "server's secret value \n callback \n sesskey" and passes that as a parameter to the JS, so that hash is valid per-session (you could also use time+userid but I guess sesskey is enough).

I was trying to think of cases where the callback would need to be decided on client-side but I couldn't see how this would happen, other than selecting from a list of options provided by the server, in which case the server can just as well pass in the callback hash to the JS the same way as it passed in the class name.

Probably this approach would only be suitable in addition to other restriction methods i.e. not letting people call absolutely anything this way.

--sam

In reply to sam marshall

Re: Securely passing callbacks from client side code

by Marina Glancy -
Picture of Core developers Picture of Moodle HQ Picture of Moodle Workplace team Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

Hi Sam,

session key is enough to prevent CSRF. Everything else that user can call inside his session does not need additional protection - calling webservice with specifying an arbitrary callback has exactly the same impact as requesting a URL by typing it in the browser location box.

In reply to Marina Glancy

Re: Securely passing callbacks from client side code

by Marina Glancy -
Picture of Core developers Picture of Moodle HQ Picture of Moodle Workplace team Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers Picture of Testers

*calling webservice with specifying arbitrary parameters

(sorry, saying that we specify "callback" is very wrong, I meant "parameters" or "callback parameters")