Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[14.0][IMP] auth_oidc: Logout the user from the auth provider #666

Open
wants to merge 17 commits into
base: 14.0
Choose a base branch
from

Conversation

wluyima
Copy link

@wluyima wluyima commented Jun 11, 2024

Added a mechanism to logout the user from the auth provider when

@OCA-git-bot
Copy link
Contributor

Hi @sbidoul,
some modules you are maintaining are being modified, check this out!

Copy link
Contributor

@ap-wtioit ap-wtioit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your efforts to make this module better. Could you run the OCA pre-commit or incoperate the suggestions from the log output: https://github.com/OCA/server-auth/actions/runs/9479497854/job/26118147534?pr=666

p = request.env["auth.oauth.provider"].sudo().browse(user.oauth_provider_id.id)
if p.logout_endpoint:
r = request.env['ir.config_parameter'].sudo().get_param('web.base.url') + redirect
logout_url = p.logout_endpoint + "?client_id=" + p.client_id + "&post_logout_redirect_uri=" + r
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the logout_endpoint may already contain query parameters:

end_session_endpoint
    REQUIRED. URL at the OP to which an RP can perform a redirect to request that the End-User be logged out at the OP. This URL MUST use the https scheme and MAY contain port, path, and query parameter components. 

https://openid.net/specs/openid-connect-rpinitiated-1_0.html

So it would be better to not add a hardcocded questionmark here but use a more dynamic solution to adding parameters.

Also it seems suspicious that the redirect url r is not encoded here. (But i have not tested it yet)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes r should be encoded when added as post_logout_redirect_uri. (e.g. when redirect is /web/login?db=devel&something=different it would possibly end up as parameter something on the logout_endpoint)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, I will update the PR

@@ -46,6 +46,10 @@ class AuthOauthProvider(models.Model):
string="Token URL", help="Required for OpenID Connect authorization code flow."
)
jwks_uri = fields.Char(string="JWKS URL", help="Required for OpenID Connect.")
logout_endpoint = fields.Char(
string="Logout URL",
help="Required for OpenID Connect to logout the user in the authorization provider upon logout in the client."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is expected to be the same value as end_session_endpoint from the provider discovery metadata, can you add a remark to the documentation that this is the value to be used.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I could also rename the property to end_session_endpoint

if user.oauth_provider_id.id:
p = request.env["auth.oauth.provider"].sudo().browse(user.oauth_provider_id.id)
if p.logout_endpoint:
r = request.env['ir.config_parameter'].sudo().get_param('web.base.url') + redirect
Copy link
Author

@wluyima wluyima Jun 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ap-wtioit is redirect expected to always be relative to the base URL? If full URLs are supported then the line above would need to be updated to be optional if redirect starts with http or https

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think only relative redirects are used in odoo but there is no technical limit for that and the redirect paramter is sent directly to werkzeug: https://github.com/odoo/odoo/blob/14.0/addons/web/controllers/main.py#L1319

so yes that's a good catch and web.base.url should only be used when the url is not starting with a protocol (i guess http(s) will suffice), also there is the possibility that the redirect is relative to /web/session/logout (e.g. ../login, or destroy) but i think this is also not used currently (and i tested it only locally for checking security)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mentioned destroy, I see it also logs out the user, do we need to override it too to logout the user in the IDP?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can only see destroy being used in one Odoo Enterprise application "project_timesheet_synchro / Synchronization with the external timesheet application" and that code upon login does not support the things added by auth_oidc so i would skip this usecase here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is this good to go?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to test it again (most likely tomorrow) but yes this looks good to me now.

p = request.env["auth.oauth.provider"].sudo().browse(user.oauth_provider_id.id)
if p.logout_endpoint:
r = redirect
if r.find('http') == -1 and r.find('https') == -1:
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have sneaked in support for full URLs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use not r.startswith("http:") and not r.startswith("https:") here. the check you used would skip adding the base url for all redirects containing http.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@ap-wtioit
Copy link
Contributor

Reviewed again, did you see my comment regarding oca-precommit? The check is still failing.

@wluyima
Copy link
Author

wluyima commented Jun 18, 2024

@ap-wtioit when do you plan to have this merged?

@ap-wtioit
Copy link
Contributor

@ap-wtioit when do you plan to have this merged?

to me it looks good now. to get it merged we need a second approval, ideally from a PSC (Project Steering Committee member) or the maintainer of the module. then it can be merged (by the maintainer or PSC).

@wluyima
Copy link
Author

wluyima commented Jun 20, 2024

@sbidoul do you mind becoming the second reviewer/approver on this PR?

@gurneyalex gurneyalex added this to the 14.0 milestone Jun 21, 2024
@sbidoul
Copy link
Member

sbidoul commented Jun 27, 2024

I will look into it. Not had time yet.

@wluyima
Copy link
Author

wluyima commented Jun 27, 2024

I see that this was added to the 14.0 milestone, it would be great to forward port the changes to later milestones too i.e. 15, 16 and 17

Copy link
Member

@sbidoul sbidoul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

Thanks for this contrib.

I made a few comments, the most important being the URL parsing logic that should use urllib.parse for robustness. I think this is important is such a security sensitive module.

Could you also add a test? The existing tests should give inspiration.

Head up about forward porting: I tested this on 16.0 and it will require some work to adapt, because Odoo now forces a local redirect (requests.redirect has a local=True argument):

https://github.com/odoo/odoo/blob/e889bd2c993a456d3077beaa3271f73e3510665e/addons/web/controllers/session.py#L85-L87

@@ -46,6 +46,12 @@ class AuthOauthProvider(models.Model):
string="Token URL", help="Required for OpenID Connect authorization code flow."
)
jwks_uri = fields.Char(string="JWKS URL", help="Required for OpenID Connect.")
logout_endpoint = fields.Char(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest naming this field end_session_endpoint to match the field in the openid configuration document. This will make the code easier to understand.

request.env["ir.config_parameter"]
.sudo()
.get_param("web.base.url")
+ r
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use urllib.parse.urljoin instead of + here. It will be better in case web.base.url ends with a /.

logout_base_url = p.logout_endpoint
params = {}
if "?" in p.logout_endpoint:
url_parts = p.logout_endpoint.split("?")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too comfortable with parsing a URL this way. What if, for instance, there is more than 1 ? in the url. It should not happen of course, but... It would be more robust to use urllib.parse.urlparse and parse_qs.

params = url_decode(url_parts[1])
params["client_id"] = p.client_id
params["post_logout_redirect_uri"] = r
logout_url = f"{logout_base_url}?{url_encode(params)}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to extract this url manipulation logic in a little class method that could be unit tested.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there documentation on how to run the test suite?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wluyima This part of the odoo documentation should get you started. Let me know if you hit any issue.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @sbidoul is there a way I can find documentation to run Odoo 14.0 locally from source? I was following the 15.0 documentation and I can't seem to install the packages in the requirements.txt file, I get this error when I try to install the packages, do you have any clues? I'm using Python 3.12.4. I'm not sure if is not compatible, is there documentation on Odoo and Python versions compatibility?

Copy link
Member

@sbidoul sbidoul Jul 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Odoo 14 I generally use python 3.8. It will for sure not work with 3.12.

I recommend installing the Odoo dependencies in a virtual environment:

python3.8 -m venv ./venv
source ./venv/bin/activate
pip install -r https://raw.githubusercontent.com/odoo/odoo/14.0/requirements.txt

But if you don't use Odoo 14, why do you do this PR to the 14.0 branch?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @sbidoul! I did say we're using Odoo 14 which is why this PR is issued against the 14.0 branch and I'm installing the dependencies in a virtual environment but I was using python 3.12, I'll try with python 3.8 and see if it works. I was actually able to later find the documentation for Odoo 14 here.

def logout(self, redirect="/web/login"):
user = request.env["res.users"].sudo().browse(request.session.uid)
if user.oauth_provider_id.id:
p = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little detail: let's use explicit variable names?

string="Logout URL",
help="Required for OpenID Connect to logout the user in the authorization provider "
"upon logout in the client, should be the value of end_session_endpoint specified by "
"the authorization provider",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we mention that when this field is set, logging out of Odoo also causes a complete logout of the identify provider?


class OpenIDLogout(Session):
@http.route("/web/session/logout", type="http", auth="none")
def logout(self, redirect="/web/login"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment or docstring referencing the spec that this implements?
https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout

@sbidoul sbidoul changed the title Logout the user from the auth provider [14.0][IMP] auth_oidc: Logout the user from the auth provider Jul 12, 2024
)
if provider.end_session_endpoint:
red_url = redirect
if not red_url.startswith("http:") and not red_url.startswith("https:"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is now unnecessary, I think, because urljoin will do the right thing.

.browse(user.oauth_provider_id.id)
)
if provider.end_session_endpoint:
red_url = redirect
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
red_url = redirect
post_logout_redirect_uri = redirect

@wluyima
Copy link
Author

wluyima commented Jul 19, 2024

@sbidoul tests have been added

@wluyima
Copy link
Author

wluyima commented Aug 1, 2024

@sbidoul @ap-wtioit tests were added, did you get a chance to take a look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants