? ? Pending

User tests: Successful: Unsuccessful:

avatar alikon
alikon
10 Nov 2020

Pull Request for Issue #31325

Summary of Changes

a 1st step to allow cors requests

  • Added global config settings for Access-Control-Allow-Origin, Access-Control-Allow-Headers and Access-Control-Allow-Methods.
  • Added preflight handler method to Joomla\CMS\Application\ApiApplication.
  • Made some changes to Joomla\CMS\Application\ApiApplication and Joomla\CMS\Router\Router needed for implementation of the preflight handler.
  • Removed obsolete code from the Joomla\CMS\Application\ApiApplication respond method.

Testing Instructions

from #31325

Assuming site A is a Joomla! 4 site and site B is another site, in a domain other than site A, wanting to consume webservices of site A

  • On site A create an API token for a Joomla! user that has adequate permissions to access the webservices.
  • On site B copy the below <script> into file administrator/components/com_content/views/articles/tmpl/default.php after line 55

if it's J3 or in file administrator/components/com_content/tmpl/articles/default.php after line 93 if it's J4.

<script type="application/javascript">
    var myHeaders = new Headers();
    myHeaders.append("X-Joomla-Token", "YOUR TOKEN");

    var requestOptions = {
        method: 'GET',
        headers: myHeaders,
        redirect: 'follow'
    };

    fetch("http:/DOMAIN OF SITE A/api/index.php/v1/content/article", requestOptions)
        .then(response => response.text())
        .then(result => console.log(result))
        .catch(error => console.log('error', error));
</script>
  • Replace DOMAIN OF SITE A in the script with the domain of site A.
  • Replace YOUR TOKEN in the script with the API token created in the first step.
  • Open the browser developer console, navigate to the content articles page and notice the errors.

Actual result BEFORE applying this Pull Request

A 404 for the preflight OPTIONS request and a Cross-Origin request blocked error for the GET request.
image

Expected result AFTER applying this Pull Request

  • apply the pr
  • Enable CORS

image

  • A valid 200 response for both the preflight OPTIONS request and the subsequent GET request
    image

Documentation Changes Required

?

de63892 10 Nov 2020 avatar alikon cors
309e5d0 10 Nov 2020 avatar alikon cors
18e2cec 10 Nov 2020 avatar alikon cors
9312464 10 Nov 2020 avatar alikon cors
7ca7d0e 10 Nov 2020 avatar alikon cors
c5d7952 10 Nov 2020 avatar alikon cors
avatar alikon alikon - open - 10 Nov 2020
avatar alikon alikon - change - 10 Nov 2020
Status New Pending
avatar joomla-cms-bot joomla-cms-bot - change - 10 Nov 2020
Category Administration com_config Language & Strings Libraries
avatar alikon alikon - change - 10 Nov 2020
The description was changed
avatar alikon alikon - edited - 10 Nov 2020
avatar alikon alikon - change - 10 Nov 2020
The description was changed
avatar alikon alikon - edited - 10 Nov 2020
avatar alikon alikon - change - 10 Nov 2020
The description was changed
avatar alikon alikon - edited - 10 Nov 2020
avatar roland-d
roland-d - comment - 10 Nov 2020

@alikon Codestyle is failing ;)

avatar alikon
alikon - comment - 10 Nov 2020

yeah... but it's a poc .....
.....ok .... lets try to fix cs

7cf36a3 10 Nov 2020 avatar alikon cs
avatar alikon alikon - change - 10 Nov 2020
Labels Added: ? ?
avatar pjdevries
pjdevries - comment - 10 Nov 2020

@alikon Cool initiative! Thanx.

I have not tested it yet (will try tomorrow), but just out of curiosity: have you tested it yourself?

avatar alikon
alikon - comment - 10 Nov 2020

yes ....
but to be fully honest i've emulated the different domain/cross origin with the help of docker......
that's why i've opened this as a Proof Of Concept

surely it is not the perfect solution.... but a starting point

avatar alikon
alikon - comment - 10 Nov 2020

and I really don't like discussion without a sense of reality

avatar pjdevries
pjdevries - comment - 10 Nov 2020

But you did test from within a browser? As opposed to using Postman (or something similar)? Docker shouldn't be a problem, since I did that myself when I ran into the problem.

I ask, because it's the browser that sends OPTION request and if I look at the code, I have the feeling it can not work.

avatar alikon
alikon - comment - 10 Nov 2020

don't want to appear like an hassle
but before to send a pr i have tested it......at least...

5d0ac16 10 Nov 2020 avatar alikon cs
avatar bembelimen
bembelimen - comment - 10 Nov 2020

Just for reference (which should not stop this PR): before it should be merged the websevice auth system has to be fixed/improved, so the token don't have to be a secret (oauth/whatever)...

avatar alikon alikon - change - 11 Nov 2020
The description was changed
avatar alikon alikon - edited - 11 Nov 2020
avatar pjdevries
pjdevries - comment - 11 Nov 2020

@alikon First an apology for questioning your integrity as a developer. I must learn to keep my big mouth shut and think before I post comments.

I tested the PR and it looks good. I do have a few suggestions though:

  • Additional global configuration settings for Access-Control-Allow-Origin, Access-Control-Allow-Headers and Access-Control-Allow-Methods with sensible defaults, as opposed to the hard coded values.

  • If not set explicitly by the above mentioned configuration setting, obtain the allowed methods for Access-Control-Allow-Methods from the requested route.

Other than that I have nothing to wish for to get this thing on the road.

the webservice auth system has to be fixed/improved, so the token don't have to be a secret (oauth/whatever)...

I guess there will always be a token of some kind that's best kept secret, but a more sophisticated authentication mechanism would most definitely be a nice next step.

avatar alikon
alikon - comment - 11 Nov 2020

@pjdevries no problem...

for the suggestions

  • Additional global configuration settings for Access-Control-Allow-Origin, Access-Control-Allow-Headers and Access-Control-Allow-Methods with sensible defaults, as opposed to the hard coded values.
  • If not set explicitly by the above mentioned configuration setting, obtain the allowed methods for Access-Control-Allow-Methods from the requested route.

i would not spent time now, i would like 1st, to listen from Release Lead and/or mantainers if this is a desiderable approach
after that we can refine...

avatar pjdevries
pjdevries - comment - 11 Nov 2020

If you change your mind, I have a working system plugin with the logic already in place :)

avatar alikon
alikon - comment - 11 Nov 2020

ok then
the branch https://github.com/alikon/joomla-cms/tree/patch-117 is open
feel free to send a pr

avatar pjdevries
pjdevries - comment - 12 Nov 2020

A bit busy right now, but I'll pick it up as soon as I can.

avatar Razzo1987 Razzo1987 - test_item - 14 Nov 2020 - Tested successfully
avatar Razzo1987
Razzo1987 - comment - 14 Nov 2020

I have tested this item successfully on 5d0ac16

Work

Before apply the patch:
immagine

After apply the patch:
immagine

After apply the patch and enable CORS:
immagine


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/31379.
avatar alikon
alikon - comment - 16 Nov 2020

i've merged the pr from @pjdevries read this alikon#70 for more details

1569ef9 17 Nov 2020 avatar alikon cs
avatar alikon alikon - change - 17 Nov 2020
The description was changed
avatar alikon alikon - edited - 17 Nov 2020
avatar alikon alikon - change - 17 Nov 2020
The description was changed
avatar alikon alikon - edited - 17 Nov 2020
5f6da96 17 Nov 2020 avatar alikon cs
7f294d0 17 Nov 2020 avatar alikon cs
e802133 17 Nov 2020 avatar alikon cs
6052a4a 17 Nov 2020 avatar alikon cs
1ec42db 17 Nov 2020 avatar alikon cs
avatar alikon alikon - change - 17 Nov 2020
Title
[poc][4.0][webservices] allow cors request
[RFC][4.0][webservices] allow cors request
avatar alikon alikon - edited - 17 Nov 2020
avatar Razzo1987 Razzo1987 - test_item - 21 Nov 2020 - Tested successfully
avatar Razzo1987
Razzo1987 - comment - 21 Nov 2020

I have tested this item successfully on 1ec42db

Re tested.

Before the patch:
image

After patch but not enable CORS:
image

After patch and enable CORS:
image
image

After patch and enable CORS with different Access-Control-Allow-Origin:
image
image

After patch and enable CORS with correct Access-Control-Allow-Origin:
image
image

Only a question:
Why the "Access-Control-Allow-Methods" haven't the dropdown list as "Request Methods To Log" ?
image


This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/31379.
avatar Razzo1987
Razzo1987 - comment - 21 Nov 2020

Access-Control-Allow-Methods doesn't work:

I set only POST:
image

but GET return value:
image

:(

avatar nikosdion
nikosdion - comment - 24 Nov 2020

@bembelimen You said:

Just for reference (which should not stop this PR): before it should be merged the websevice auth system has to be fixed/improved, so the token don't have to be a secret (oauth/whatever)...

I normally wouldn't comment on this but seeing buzzword mythology, giving well-understood technologies mythical properties they never had and could never had in a world where magic is not real, I feel obliged to comment.

The only difference in an OAuth/OAuth2 authentication flow is how the token is generated, not how it is stored or used. It doesn't matter if it's a short-lived access token with a refresh token to non-interactively reauthorise the application when the access token expires, or a long-lived access token which never expires. Either way the tokens are secret, stored by the remote application and used in requests to the API server. OAuth2 further requires the client to store a client and secret key which are also secret and API server specific. The latter property doesn't mesh very well with Joomla which is a mass-distributed application, not a centralised service.

OAuth2 tokens are sent in the Authorization: Bearer <TOKEN> header. This is also supported by Joomla Token and is, in fact, the preferred method. However, Joomla is a mass-distributed application and not all servers work properly with this header. It requires a bit of fumbling with their configuration. Hence the fallback X-Joomla-Token header. I explicitly stated that in my PR and it's also in the code comments which I'm sure you read but didn't understand – you wouldn't comment on something you haven't read the code for and don't understand, right?

For what it's worth, Joomla Token was fashioned after the OAuth2 authorisation using long-term tokens taking into account that Joomla is a single tenant, multi-user, mass-distributed application instead of a centralised service.

PS: OAuth2 tokens, just like the Joomla Token, are vulnerable to replay attacks. Avoiding replay attacks requires request signing, e.g. how Amazon AWS does it with their V4 and even their V2 API and even that only works to a certain extent (there is still a 15 minute window in AWS where replay attacks are possible). However, this is atypical for web services. The reasonable assumption in pretty much every web service is that the request goes through HTTPS making it unlikely that its headers and/or contents will be divulged to a MITM. Joomla SHOULD lock its API to HTTPS-only.

avatar alikon alikon - change - 24 Nov 2020
Labels Added: ?
avatar bembelimen
bembelimen - comment - 24 Nov 2020

Thank you @nikosdion I fully agree, my pointer was not to define oAuth as THE solution for this PR, just more like: don't merge before this problem is solved.
To make it clear: oAuth does not solve the security issue here.

avatar nikosdion
nikosdion - comment - 24 Nov 2020

@bembelimen I think you are confused. You seem to confuse how someone uses any form of authentication with whether the method in and of itself is secure.

First, let's start with the fact that token authentication is actually more secure than sending usernames and passwords. I strongly recommend reading Phil Sturgeon's book “Build APIs You Won't Hate” (http://leanpub.com/build-apis-you-wont-hate), Chapter 9 “Authentication”. He explains why REST (which is an acronym for REpresentational State Transfer) doesn't do cookies and why forms of authentication other than tokens are a grave security issue.

In this book and chapter you will also see a far more in-depth discussion on the allusion I made to long-term vs short-term tokens. Then consider that discussion against the context of the book (you have a centralised API service) versus the context of Joomla (mass-distributed application, each site is its own API service) to understand why I chose to use personal, long-term tokens.

As I said, every authentication method, stateless or stateful, is weakened if not made redundant by unencrypted plain HTTP traffic. It doesn't matter if your authentication method is a cookie or a token. If it's sent over HTTP it can be subjected to a MITM attack. That's why I said that Joomla's API application should be locked to HTTPS only.

Since Joomla is building a REST API it cannot use cookies anyway. Cookies are persistent state – they were invented to solve the problem that web requests are inherently stateless. REST is, by definition, always stateless. It's in the name. Joomla's API is REST, therefore stateless.

The way the Joomla Token is implemented means that you cannot revoke permission to a specific application that's using it. You have to reset it and set up the token on all remaining consumers. This may be problematic and I would be the first to admit it. It can be improved by allowing for more than one personal access tokens to be created, with labels and independent revocation (deletion), using the same UI approach I followed in the WebAuthn plugin. Remember that when I contributed the Joomla Token we thought beta was around the corner and I really had to provide the minimum viable product that required the least amount of testing and disruption. Good news is that I had already taken that into account when designing the token format. I made it forward compatible on purpose.

Finally, the most likely source of your confusion: usage versus inherent properties of the authentication method. You might be worried that JavaScript (user visible) has the token embedded in it. You are in a sense correct: if it's not implemented correctly it can be EXTREMELY insecure and I do understand why is confused you. However, that's the wrong way to think about it. It's like saying keys are insecure because someone can leave their key under the doormat. Wrong use does not equal an inherent security issue of the authentication method. Keys are more secure than keyless entry and electronic keys using FIDO2 are far more secure than physical keys in the same way that tokens are more secure than username/password pairs and OAuth2 issued, short-lived tokens are more secure than long-term tokens. So please let me break this down into two use cases to explain what is the correct use and why.

One use case is that you are accessing your own account, on your own site, through the remote service. So whether you see your own token in the JavaScript of your own browser is largely irrelevant. You can hack yourself and nobody but yourself. Big effin' whoop.

The other use case is that as an administrator you want to set up site B to serve content from site A using your personal access token from site A. In this case you MUST NOT use the token directly in the Javascript that everyone and their dog will see in the page output! You can proxy it through your own site. If you don't get the concept use issues.joomla.org. You know that it's an interface to GitHub, right? It uses a (secret!) access token issued for the Joomla organisation on GitHub. Every time you interact with that site it sends requests to GitHub's RESTful API using this token. Clearly, it is secure. It is secure because privileged access is proxied through the site's server side which is a black box to the client (user).

To sum it up, the Joomla Token is not inherently insecure but it can be improved by allowing for more than one tokens per user. Further to that, nothing stops you from creating an OAuth2 server for Joomla. I am not convinced that it's practical because the UX is terrible: instead of copying a token auto-generated for you you would need to first create a client/secret key pair and paste it to the remote consumer, then authenticate against your own site. The only thing it would solve, if implemented, is that instead of a long-term token it would send a short-lived access and a long-lived refresh token. However, both would still need to be stored in the remote client. The only added protection you'd get is a limited form of replay attack protection, i.e. an access token recovered through a MITM attack can only be used for the limited amount of time it is active (typically 30' to a day). This was largely irrelevant at the moment because of the very limited nature of the Joomla API in Joomla 4.0. When moving towards a more generic API in 4.0 you can and should move towards scoped authentication which would apply additional access control on top of the access granted through Joomla's User Groups. But that's very much a discussion for the future. Let's make sure the API can walk before we teach it how to run and then fly.

avatar bembelimen
bembelimen - comment - 24 Nov 2020

Thanks for the explaination, I'm aware of it.
Again, I did not say anything about the security of the token itself, just that it should not be visible/used in JS in the current implementation, nothing more.

avatar nikosdion
nikosdion - comment - 24 Nov 2020

@bembelimen Oh, for crying out loud! Here is what you originally said, word for word, emphasis mine:

before it should be merged the websevice auth system has to be fixed/improved, so the token don't have to be a secret (oauth/whatever)...

If you really knew everything I told you today why did you write something that is diametrically opposite to what you already knew?

I have no time or respect for people who act like toddlers caught with the now empty cookie jar at hand. Grow up and then we can talk.

avatar wilsonge
wilsonge - comment - 1 Dec 2020

I think this is mergeable with two good tests. The CORS and OPTIONS response both work independently of the auth method.

I agree however - The docs need to state we strongly recommend changing the webservices auth plugin if you are going to use this on a frontend javascript site. Generally you're going to run an SPA then you using oAuth with authorization grant which never sends a secret (password or token) because you do the auth request on the target website. You just get a short lived token back. And yes I do also agree that this token method is still a billion times better than the basic auth we had before.

I think all 3 of us know pretty well how oauth works - so not going to get into it further than - we need to make sure the docs lead people to make secure decisions :)

avatar alikon
alikon - comment - 1 Dec 2020

i've read somewhere....

Let's make sure the API can walk before we teach it how to run and then fly.

avatar wilsonge wilsonge - change - 2 Dec 2020
Title
[RFC][4.0][webservices] allow cors request
[4.0][webservices] Allow customizing CORS headers and add support for OPTIONS method
avatar wilsonge wilsonge - edited - 2 Dec 2020
avatar alikon alikon - change - 4 Dec 2020
Labels Removed: ?
avatar wilsonge wilsonge - change - 16 Dec 2020
Status Pending Fixed in Code Base
Closed_Date 0000-00-00 00:00:00 2020-12-16 18:03:11
Closed_By wilsonge
avatar wilsonge wilsonge - close - 16 Dec 2020
avatar wilsonge wilsonge - merge - 16 Dec 2020
avatar wilsonge
wilsonge - comment - 16 Dec 2020

Tested this locally myself and seems to work fine. I'm merging this for a first pass and we can continue iterating as required

avatar alikon
alikon - comment - 16 Dec 2020

🎆

just a little glitch #31680
😃

Add a Comment

Login with GitHub to post a comment