[Request] Make anonymous API and sockjs usage easier #1681

Open
taxilian opened this Issue Jan 2, 2017 · 4 comments

Projects

None yet

3 participants

@taxilian
taxilian commented Jan 2, 2017 edited

What were you doing?

I am creating a "printer aggregation" page which allows me to view the webcam feed and status of my three octoprint servers from a single page. I have a virtual host in nginx using reverse proxy configuration which correctly allows me to use the three printers on the same domain under different subpaths and I can view the camera feed from each.

Here is the code, feel free to use as an example on how to do this if you want: https://gist.github.com/taxilian/87e83fd9e4d9c1a4408f2fb67524a516

Now I'm trying to fetch the current status -- temperature, current job (if any), est time remaining, and maybe status bar or other such info.

What did you expect to happen?

I expected this to be pretty trivial to grab using either the Octoprint JS api or else using the REST api via ajax requests.

What happened instead?

It seems that a) the Octoprint JS api expects that you'll never need to talk to more than one octoprint server in a given page and b) whether using the provided js library or using the REST api I need an API token -- even for "anonymous" request. I think I can work around (a) either by using the REST api or by changing credentials and paths for every single request; (b) is particularly vexing, though, because I don't want to do anything that would require authentication and I most definitely do not want to expose my API key on a public page, but there isn't an easy way to get the anonymous API key.

Currently I'm using AJAX to request the main octoprint page (which is around 480kb), truncating to about 3kb, and then using a regular expression to extract the anonymous UI key. This is very slow and definitely not ideal, but it works.

Branch & Commit or Version of OctoPrint

1.3.0

Specific Request

I have a few thoughts on how I probably would fix this, but as I'm not familiar with the reasons that you did things in the specific ways you did them I'm going to just list the variants I can think of that would satisfy my request.

I'm also willing to do the coding if you'll tell me which method you'd be willing to accept =] I just don't want to do it one way and find you're not willing to accept it for reasons I don't have the context to anticipate.

  1. (preferred) All API calls and sockjs requests when not accompanied by an API key would just revert to "anonymous" permissions. This way I could just use whichever method I want and could get the basic printer status that you can get from the instance without logging in. This would also greatly simplify any other integrations which wanted to be able to monitor multiple systems.

  2. If there is a reason that the above should not be used (and I don't understand why it's not the default, so I'm guessing there probably is) then a REST endpoint which would just return the anonymous API key would provide a vastly better option to grabbing the entire home page as I'm doing now =]

I have read the FAQ. I also feel your pain on stupid "already-answered" questions and have attempted to avoid creating another one =] I believe this could be solved with a plugin, but it seems to me something that would be better solved in the main project since any solution to extract the anonymous API key would likely be hacky at best and rely on implementation details that are probably better off without the plugin knowing about.

@foosel
Owner
foosel commented Jan 9, 2017

Just a quick heads-up: I just came back from a long overdue vacation today and am currently wading through a ton of issues etc. I'll get back to this ticket once I've gotten around to the most pressing of these.

@foosel
Owner
foosel commented Jan 19, 2017

Sorry again for the delay.

Back in ancient history, when I added the requirement for this anonymous UI_API_KEY, the motivation was that general API access should be allowed to be disabled, with only the UI being allowed to access the endpoints. So the goal was that even anonymous access was forbidden, unless the API was explicitly enabled - and if I remember correctly that was even how OctoPrint was shipped for the first year or so, with the API set to off.

These days, I have to say I'm not sure myself if that makes much sense anymore, especially since it complicates a lot of things. So from my own point of view I'd say, let's just get rid of that UI_API_KEY and make requests without a key anonymous and potentially also rate limited to a certain degree. The problem is that I'm not sure how many people I'd piss off with such a decision - I don't know if there are still people out there who actually have set the API to disabled.

A solution would be to still keep the stock UI utilizing the UI_API_KEY, and by default also allow anonymous access to the API as described above, unless the API is explicitly disabled in which case no anonymous access is possible... Then again, I'm seriously leaning towards getting rid of this ancient construct.

What do you think?

Also, you bring up a very valid point with regards to the javascript client and multiple instances. I have to admit that I didn't think about that when I created it, but it would have made a lot of sense. Meh. Problem here is backwards compatibility - it would be easy to just switch it over to an instance based approach, but if there are already people who are depending on the current form of the client that code will break. Really meh. Where were you when I first wrote that client a year or so ago? :)

@taxilian

No worries on the delay; I have it working using the hack I mentioned, I just feel that's somewhat less than ideal =]

Regarding the question of API keys and with that background in mind, I would suggest that the UI_API_KEY be deprecated but left in place; as you suggest, if the API is disabled then disabling anonymous API usage makes sense, but it's worth considering that the only way to effectively do that would be to also disable any api access from the UI when the user is not logged in. Even as things are now, all you have to do is download the UI page and use a regex to strip the API key from the html file. It seems like what the setting should actually be is whether or not anonymous access to the UI is allowed and if it's disabled then disable all APIs without an authenticated API key. At that point you can get rid of the API key for anonymous access and fall back to "if no key, default to anonymous privileges".

As I mentioned I'm happy to help with this -- I'm a tiny bit rusty but still pretty strong in python but I might need some help finding where in the code to get started as I'm strapped for time =]

On the javascript side what I'd recommend is what should be a minor refactor so that there are two valid usage patterns. I don't remember right this second what the specific names are, but something like this:

Old and still valid usage pattern:

OctoPrint.options.apikey = 'deadbeef';
OctoPrint.options.baseurl = '/mk2';
var histDfd = OctoPrint.printer.getFullState({history: false, exclude: ["sd"]});
histDfd.then(/* .... */);

New usage pattern:

var client = new OctoPrint.Client({ apikey: 'deadbeef', baseurl: '/mk2' });
var histDfd = client.printer.getFullState({history: false, exclude: ["sd"]});
histDfd.then(/* .... */);

Basically to do this all you'd have to do is wrap the current library in an object and then do something like:

OctoPrint = new OctoPrintClient();
OctoPrint.Client = OctoPrintClient;

window.OctoPrint = OctoPrint;

There are better patterns you can use for the last bit to make it more compatible with things like require.js, etc, but this would give you a starting point and should be a pretty easy refactor. I'd be happy to work on this as well, but my time consideration may make the changes a bit slow =] Javascript is where I spend most of my time these days, though I do have a lot of python and C++ experience as well.

As to where I was a year ago -- I was just learning the basics of 3d printing and had just gotten my first printer ;-)

@foosel foosel added a commit that referenced this issue Jan 20, 2017
@foosel Allow multiple instances of the JS client
We now have a global OctoPrintClient, which is the class from which
all clients are derived, and a global OctoPrint, which is a single
instance already setup and ready to use in case we only need one.

It would be cleaner to have clients create that singular instance
themselves, but we need to maintain backward compatibility for now
with how we established the client to work with the 1.3.0 release.

New clients can be create with

    client = new OctoPrintClient({ /* options */ });

Alternatively the options can be left out and set at a later point:

    client = new OctoPrintClient();
    /* ... */
    client.options = { /* options */ };

Individual client components register themselves with OctoPrintClient
via OctoPrintClient.registerComponent(name, component) from the
component JS files. Just like before their instances are then
available in the individual client instances under "<client>.<name>",
 e.g. "OctoPrint.files".

Plugin components register themselves with OctoPrintClient via
OctoPrintClient.registerPluginComponent(name, component) from the
component JS files. Just like before their instances are then
available in the individual client instances under "<client>.plugins
.<name>", e.g. "OctoPrint.plugins.softwareupdate".

This should make it possible to create dashboard pages utilizing the
JS client that monitor the status of multiple OctoPrint instances,
without workarounds such as having to swap out the options globally
before each request.

See #1681 for the corresponding discussion.
b1d8a6a
@foosel
Owner
foosel commented Jan 20, 2017

Refactoring the JS client isn't as trivial as the above due to how it assembles itself from multiple components JS files. However I took a shot at it today and I think I've found something that works, see the above commit + its comment. I fear that the code might cause seasoned JS devs to start to cry since it's somewhat hackish, but it allows full backwards compatibility to everything that was already documented and might since be in use.

I've only pushed it to a feature branch for now since I want to give it some more testing with the real application to make sure I didn't overlook something critical, but you might want to play with it to see if it solves your problems as well.

About the API key, the relevant parts would be this and this. I have to admit that it's been ages since I touched that stuff and I'd need to check in which case which one is used. If my memory does serve me at least slightly well though, apiKeyRequestHandler is also called for all requests (so even the anonymous ones) and responsible for checking if a valid key is provided. For resources that require a user session, restricted_access then verifies that.

I think in order to disable the UI_API_KEY requirement and simply fall back to anonymous requests if no login session is present it would be sufficient to adjust apiKeyRequestHandler to only require a UI_API_KEY if general API support has been disabled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment