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

[REST Auth] API tokens & openhab:users console command #1735

Merged
merged 12 commits into from
Oct 25, 2020

Conversation

ghys
Copy link
Member

@ghys ghys commented Oct 19, 2020

This adds API tokens as a new credential type. Their format is:
oh.<name>.<random chars>

They are stored hashed in the user's profile, and can be listed, added
or removed managed with the new openhab:users console command.

Currently the scopes are still not checked, but ultimately they could
be, for instance a scope of e.g. user admin.items would mean that the
API token can be used to perform user operations like retrieving info
or sending a command, and managing the items, but nothing else -
even if the user has more permissions because of their role (which
will of course still be checked).

Tokens are normally passed in the Authorization header with the Bearer
scheme, or the X-OPENHAB-TOKEN header, like access tokens.
As a special exception, API tokens can also be used with the Basic
authorization scheme, even if the allowBasicAuth option is not
enabled in the "API Security" service, because there's no additional
security risk in allowing that. In that case, the token should be
passed as the username and the password MUST be empty.

In short, this means that all these curl commands will work:

  • curl -H 'Authorization: Bearer <token>' http://localhost:8080/rest/inbox
  • curl -H 'X-OPENHAB-TOKEN: <token>' http://localhost:8080/rest/inbox
  • curl -u '<token>' http://localhost:8080/rest/inbox (will prompt for a password, just hit Return);
  • curl -u '<token>:' http://localhost:8080/rest/inbox (will not prompt for a password)
  • curl http://<token>@localhost:8080/rest/inbox

2 REST API operations were adding to the AuthResource, to allow
authenticated users to list their tokens or remove (revoke) one.

Self-service for creating a token or changing the password is more
sensitive so these are handled with a servlet and pages devoid
of any JavaScript instead of REST API calls.

Closes openhab/openhab-webui#332

Signed-off-by: Yannick Schaus github@schaus.net

@ghys
Copy link
Member Author

ghys commented Oct 19, 2020

Examples:

openhab> openhab:users
Usage: openhab:users list - lists all users
Usage: openhab:users add <userId> <password> <role> - adds a new user with the specified role
Usage: openhab:users remove <userId> - removes the given user
Usage: openhab:users changePassword <userId> <newPassword> - changes the password of a user
Usage: openhab:users listApiTokens - lists the API keys for all users
Usage: openhab:users addApiToken <userId> <tokenName> <scope> - adds a new API token on behalf of the specified user for the specified scope
Usage: openhab:users rmApiToken <userId> <tokenName> - removes (revokes) the specified API token
Usage: openhab:users clearSessions <userId> - clear the refresh tokens associated with the user (will sign the user out of all sessions)
openhab> openhab:users add ysc P@ssword1 administrator
ysc (administrator)
User created.
openhab> openhab:users list
ysc (administrator)
openhab> openhab:users changePassword ysc P@ssword2
Password changed.
openhab> openhab:users addApiToken ysc service1 admin
oh.service1.IR71uwmszu0KRBEHo6GUC1BIJh7dHhv9yobyPCqwPgiOHFCT7sRjEcE3R6XfT9y6LsnR9ntk9KDGY5kchy1uBw
openhab> openhab:users addApiToken ysc service2 admin
oh.service2.GXOeP2weDRpHLXWirACcZfaz9FHh7PJhodMwHnt8NHCJWYSaSqNidvCdztHguCSQtBG885DgrWV8F3aaQQwPg
openhab> openhab:users addApiToken ysc service3 admin
oh.service3.XCIhrBGOQiNBrVdiFt2VopNgoVmQk75q9jZeOjJl9PEJpiK9V8pT9XvyKfSpoGtfmUCja9FwilHdQUL5eWDMPw
openhab> openhab:users listApiTokens
user=ysc (administrator), name=service1, createdTime=Mon Oct 19 20:17:40 CEST 2020, scope=admin
user=ysc (administrator), name=service2, createdTime=Mon Oct 19 20:17:46 CEST 2020, scope=admin
user=ysc (administrator), name=service3, createdTime=Mon Oct 19 20:17:50 CEST 2020, scope=admin
openhab> openhab:users rmApiToken ysc service1
API token revoked.
openhab> openhab:users rmApiToken ysc service2
API token revoked.
openhab> openhab:users rmApiToken ysc service3
API token revoked.
openhab> openhab:users listApiTokens
openhab> 

Note that the tokens are only printed on the console after they've been created, and they should be copy-pasted immediately, because they won't be retrievable afterwards.

ghys added a commit to ghys/openhab-core that referenced this pull request Oct 19, 2020
(I included these fixes in openhab#1735 but extracted them in a stanalone
PR because it's easier to review and a little more urgent.)

As a result of the refactoring in openhab#1713, the operations annotated with
`@RolesAllowed` containing `Role.USER` are not anymore automatically
considered accessible to all users, regardless of their actual roles.

4 operations are therefore now denied to admins if they only have the
`Role.ADMIN` role, as the first admininistrator is created only with
that role the UI encounters unexpected access denied errors and breaks.
(See openhab/openhab-webui#422).

Closes openhab/openhab-webui#422.

Signed-off-by: Yannick Schaus <github@schaus.net>
@ghys ghys mentioned this pull request Oct 19, 2020
kaikreuzer pushed a commit that referenced this pull request Oct 20, 2020
(I included these fixes in #1735 but extracted them in a stanalone
PR because it's easier to review and a little more urgent.)

As a result of the refactoring in #1713, the operations annotated with
`@RolesAllowed` containing `Role.USER` are not anymore automatically
considered accessible to all users, regardless of their actual roles.

4 operations are therefore now denied to admins if they only have the
`Role.ADMIN` role, as the first admininistrator is created only with
that role the UI encounters unexpected access denied errors and breaks.
(See openhab/openhab-webui#422).

Closes openhab/openhab-webui#422.

Signed-off-by: Yannick Schaus <github@schaus.net>
Copy link
Member

@wborn wborn left a comment

Choose a reason for hiding this comment

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

It's nice to have some commands for this. 👍
I quickly scrolled through the code and saw a few minor improvements.

This adds API tokens as a new credential type. Their format is:
`oh.<name>.<random chars>`

The "oh." prefix is used to tell them apart from a JWT access token,
because they're both used as a Bearer authorization scheme, but there
is no semantic value attached to any of the other parts.

They are stored hashed in the user's profile, and can be listed, added
or removed managed with the new `openhab:users` console command.

Currently the scopes are still not checked, but ultimately they could
be, for instance a scope of e.g. `user admin.items` would mean that the
API token can be used to perform user operations like retrieving info
or sending a command, _and_ managing the items, but nothing else -
even if the user has more permissions because of their role (which
will of course still be checked).

Tokens are normally passed in the Authorization header with the Bearer
scheme, or the X-OPENHAB-TOKEN header, like access tokens.
As a special exception, API tokens can also be used with the Basic
authorization scheme, **even if the allowBasicAuth** option is not
enabled in the "API Security" service, because there's no additional
security risk in allowing that. In that case, the token should be
passed as the username and the password MUST be empty.

In short, this means that all these curl commands will work:
- `curl -H 'Authorization: Bearer <token>' http://localhost:8080/rest/inbox`
- `curl -H 'X-OPENHAB-TOKEN: <token>' http://localhost:8080/rest/inbox`
- `curl -u '<token>[:]' http://localhost:8080/rest/inbox`
- `curl http://<token>@localhost:8080/rest/inbox`

2 REST API operations were adding to the AuthResource, to allow
authenticated users to list their tokens or remove (revoke) one.
Self-service for creating a token or changing the password is more
sensitive so these should be handled with a servlet and pages devoid
of any JavaScript instead of REST API calls, therefore for now they'll
have to be done with the console.

This also fixes regressions introduced with openhab#1713 - the operations
annotated with @RolesAllowed({ Role.USER }) only were not authorized
for administrators anymore.

Signed-off-by: Yannick Schaus <github@schaus.net>
Reusing the password salt is bad practice, and changing the
password changes the salt as well which makes all tokens
invalid.

Put the salt in the same field as the hash (concatenated
with a separator) to avoid modifying the JSON DB schema.

Signed-off-by: Yannick Schaus <github@schaus.net>
The X-OPENHAB-TOKEN header now has priority over the Authorization
header to credentials, if both are set.

Signed-off-by: Yannick Schaus <github@schaus.net>
@ghys
Copy link
Member Author

ghys commented Oct 20, 2020

FTR, I'm not too happy with passwords being provided in clear on the command line (they are stored in the command history).
In Karaf you can set censor/mask properties to command arguments to hide them from view (apache/karaf@5228a23), but the openHAB console wrapper doesn't seem to allow that.

Signed-off-by: Yannick Schaus <github@schaus.net>
@ghys
Copy link
Member Author

ghys commented Oct 20, 2020

Finally went ahead and implemented the self-service pages for changing passwords and creating API tokens, at /changePassword and /createApiToken respectfully.

localhost_8080_changePassword(iPad Mini)
localhost_8080_changePassword(iPad Mini) (1)
localhost_8080_createApiToken(iPad Mini)
localhost_8080_createApiToken(iPad Mini) (1)

Signed-off-by: Yannick Schaus <github@schaus.net>
Copy link
Member

@wborn wborn left a comment

Choose a reason for hiding this comment

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

Thanks a lot for your awesome work!
I've added a few more (minor) comments below.

@wborn wborn requested a review from a team October 22, 2020 09:47
@wborn
Copy link
Member

wborn commented Oct 22, 2020

It would be nice if another maintainer can also have a look since this deals with security.

@wborn wborn added enhancement An enhancement or new feature of the Core REST/SSE labels Oct 22, 2020
Signed-off-by: Yannick Schaus <github@schaus.net>
Signed-off-by: Yannick Schaus <github@schaus.net>
Signed-off-by: Yannick Schaus <github@schaus.net>
@lolodomo
Copy link
Contributor

Just for helping any user, would it be possible to mention in the help of the new commands what values can be used for the <scope> parameter ? I see in your examples that you use admin but are there other possible values ?

In the case of my Remote openHAB binding that uses the openHAB REST API, in case the remote OH3 server was setup to require authorization for normal REST API, I understand I will have to create a token using your new console command and then use this token in the binding by injecting it in the header of each REST API call. Is it correct ?

@ghys
Copy link
Member Author

ghys commented Oct 22, 2020

The scope model is not clear yet, but I'll suggest something like this:

  • Missing/empty scope, or all => no scope restrictions, authorize everything the user's role(s) normally allow (not sure it's a good idea)
  • user => operations to interact with the system as a user: SSE events, view items, sitemaps, pages, persisted data; send item updates & commands; audio & voice; and their own profile (sessions & tokens);
  • admin => operations to change the system: full access on things, inbox & discovery, items & links & metadata, UI components, rules, services, system info, addons & binding configuration...
  • Eventually we'll have more granular scopes like admin.things, admin.rules...

in case the remote OH3 server was setup to require authorization for normal REST API, I understand I will have to create a token using your new console command and then use this token in the binding by injecting it in the header of each REST API call. Is it correct ?

yes currently, the "user" operations are unsecured but in case they are (you can switch that on now), you'll have to provide a token to access even the most basic operations that expose data, if you don't need admin access to make changes to the configuration then a scope of user should be fine. Normally you'll be able to specify multiple scopes (space-separated).
You can generate a token with the console command or with the /createApiToken page, see above. There will be a list in the UI in the user's profile page for them to view their current tokens and allowing to create new ones.

@lolodomo
Copy link
Contributor

Ok thank you, I am waiting for the merge of this PR to test with my Remote openHAB binding.

Signed-off-by: Yannick Schaus <github@schaus.net>
@wborn
Copy link
Member

wborn commented Oct 23, 2020

Many thanks for addressing all my comments @ghys.

I just did some testing on a local build and it seems that duplicate sessions end up in users.json, they also show up in the UI so the number of user sessions did not match what I expected, so I was triggered to investigate:

  "johndoe": {
    "class": "org.openhab.core.auth.ManagedUser",
    "value": {
      "name": "johndoe",
      "passwordHash": "SZXFDGrobzJMLiFNQFvQJyhkpQePuIfPDrFM2wncIxCnNszAPYBnpd/vU0GqTDHgMAB9erA3wfTXttaXyx4+HQ\u003d\u003d",
      "passwordSalt": "/L85mMFRatxrpLgsM+9JlawfhSPbez2GGTe7y1emvfzlVZOsJlnP9RVxtDA7w+X8YX8wJbMz2EJHTfaCm+WM9w\u003d\u003d",
      "roles": [
        "administrator"
      ],
      "sessions": [
        {
          "sessionId": "ef5822a1-91bb-47f5-82e1-c6390b9e9fb6",
          "refreshToken": "da8fe178ab344076bf80438fd5f5f14f",
          "createdTime": "Oct 23, 2020, 11:52:06 AM",
          "lastRefreshTime": "Oct 23, 2020, 11:52:06 AM",
          "clientId": "http://localhost:8080",
          "redirectUri": "http://localhost:8080",
          "scope": "admin",
          "sessionCookie": true
        },
        {
          "sessionId": "ef5822a1-91bb-47f5-82e1-c6390b9e9fb6",
          "refreshToken": "da8fe178ab344076bf80438fd5f5f14f",
          "createdTime": "Oct 23, 2020, 11:52:06 AM",
          "lastRefreshTime": "Oct 23, 2020, 11:52:06 AM",
          "clientId": "http://localhost:8080",
          "redirectUri": "http://localhost:8080",
          "scope": "admin",
          "sessionCookie": true
        },
        {
          "sessionId": "ddb26127-3942-43e8-a519-ec5b46cee9dc",
          "refreshToken": "0298b62ce5f444f296684627b880ed21",
          "createdTime": "Oct 23, 2020, 11:52:17 AM",
          "lastRefreshTime": "Oct 23, 2020, 11:53:18 AM",
          "clientId": "http://localhost:8080",
          "redirectUri": "http://localhost:8080",
          "scope": "admin",
          "sessionCookie": true
        }
      ],
      "apiTokens": []
    }
  },

But using multiple users does work nice as do the commands I've tested 👍

@ghys
Copy link
Member Author

ghys commented Oct 23, 2020

Strange, I've never noticed these session duplications before, maybe it's due to a change in this PR...?

@wborn
Copy link
Member

wborn commented Oct 23, 2020

It is reproducible when you sign out of your session using the UI.
Instead of removing the session from the file it is duplicated.

Signed-off-by: Yannick Schaus <github@schaus.net>
@wborn
Copy link
Member

wborn commented Oct 23, 2020

Shouldn't there also be a DELETE operation for a specific session in TokenResource? If you click the delete button on whatever session in the UI it always navigates to /logout and thus deletes the current session.

Signed-off-by: Yannick Schaus <github@schaus.net>
@ghys
Copy link
Member Author

ghys commented Oct 23, 2020

Shouldn't there also be a DELETE operation for a specific session in TokenResource? If you click the delete button on whatever session in the UI it always navigates to /logout and thus deletes the current session.

Are you sure? I initially thought of it as the counterpart to /auth/token so it accepts the application/x-www-form-urlencoded content type with an id parameter which is the first part of the session id.
But a DELETE operation could make sense as well...

@wborn
Copy link
Member

wborn commented Oct 24, 2020

I didn't notice there was an id parameter in the logout operation to make this work. So that way there is indeed no need for an operation for deleting sessions . I'll retest deleting other sessions and see if I can make some sense of what happens.

While looking at the code in TokenResource, I also noticed some null analysis warnings. It would be nice to see those fixed too. 🙂

@wborn
Copy link
Member

wborn commented Oct 24, 2020

I'll retest deleting other sessions and see if I can make some sense of what happens.

I see that it can properly delete other sessions but it looks like the frontend has some logic to also remove its own session data in the browser regardless of the session it deletes.

@openhab-bot
Copy link
Collaborator

This pull request has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/openhab3-forgot-password/107069/2

1 similar comment
@openhab-bot
Copy link
Collaborator

This pull request has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/openhab3-forgot-password/107069/2

Signed-off-by: Yannick Schaus <github@schaus.net>
@ghys
Copy link
Member Author

ghys commented Oct 25, 2020

While looking at the code in TokenResource, I also noticed some null analysis warnings. It would be nice to see those fixed too. 🙂

Done.

Not sure what you mean by:

see that it can properly delete other sessions but it looks like the frontend has some logic to also remove its own session data in the browser regardless of the session it deletes.

It shouldn't be "regardless of the session it deletes", normally it only does that if you click "Sign out of this session".

Copy link
Member

@wborn wborn left a comment

Choose a reason for hiding this comment

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

It all seems to work well now. 👍 The deleting other sessions issue doesn't seem to be related to these changes so I'll create an issue for that.

@lolodomo
Copy link
Contributor

@ghys: I just tried the new mode where the user REST API is fully secured.
The remote openHAB binding is working when you provide the expected token. Good.

In this mode, I tried to launch Basic UI and I was surprised to see that it works, even if it is without SSE subscriptions.
Is it expected ? That would mean that some RES API are still unsecured in this mode ?

@ghys
Copy link
Member Author

ghys commented Oct 28, 2020

In this mode, I tried to launch Basic UI and I was surprised to see that it works, even if it is without SSE subscriptions.
Is it expected ? That would mean that some RES API are still unsecured in this mode ?

Basic UI renders its pages using servlets, so it's not unexpected that it "works" (displays something) because the auth layer only applies to the REST API. You won't be able to send commands or get updates since those parts are handled with API calls.

@kaikreuzer
Copy link
Member

@ghys How easy would it be to adapt our HttpContextFactoryServiceImpl (which is used by those servlets) to also consider the auth configuration?

@lolodomo
Copy link
Contributor

I will try again but I believe BasicUI was displaying the current items values.

@ghys
Copy link
Member Author

ghys commented Oct 28, 2020

How easy would it be to adapt our HttpContextFactoryServiceImpl (which is used by those servlets) to also consider the auth configuration?

I think we can forego the need for a token for the servlets and simply rely on the session cookie - i.e. the user would need to open a session with the main UI first and then the cookie will identify the session which could be reused by Basic UI and others.
However Basic UI still needs to make API calls and for that it will need a token. Basic UI can reuse the refresh token that the main UI puts in the local storage to get an access token (that's what HABPanel does).

@kaikreuzer kaikreuzer added this to the 3.0.0.M2 milestone Nov 2, 2020
splatch pushed a commit to ConnectorIO/copybara-hab-core that referenced this pull request Jul 11, 2023
(I included these fixes in openhab#1735 but extracted them in a stanalone
PR because it's easier to review and a little more urgent.)

As a result of the refactoring in openhab#1713, the operations annotated with
`@RolesAllowed` containing `Role.USER` are not anymore automatically
considered accessible to all users, regardless of their actual roles.

4 operations are therefore now denied to admins if they only have the
`Role.ADMIN` role, as the first admininistrator is created only with
that role the UI encounters unexpected access denied errors and breaks.
(See openhab/openhab-webui#422).

Closes openhab/openhab-webui#422.

Signed-off-by: Yannick Schaus <github@schaus.net>
GitOrigin-RevId: d262b6f
splatch pushed a commit to ConnectorIO/copybara-hab-core that referenced this pull request Jul 11, 2023
This adds API tokens as a new credential type. Their format is:
`oh.<name>.<random chars>`

The "oh." prefix is used to tell them apart from a JWT access token,
because they're both used as a Bearer authorization scheme, but there
is no semantic value attached to any of the other parts.

They are stored hashed in the user's profile, and can be listed, added
or removed managed with the new `openhab:users` console command.

Currently the scopes are still not checked, but ultimately they could
be, for instance a scope of e.g. `user admin.items` would mean that the
API token can be used to perform user operations like retrieving info
or sending a command, _and_ managing the items, but nothing else -
even if the user has more permissions because of their role (which
will of course still be checked).

Tokens are normally passed in the Authorization header with the Bearer
scheme, or the X-OPENHAB-TOKEN header, like access tokens.
As a special exception, API tokens can also be used with the Basic
authorization scheme, **even if the allowBasicAuth** option is not
enabled in the "API Security" service, because there's no additional
security risk in allowing that. In that case, the token should be
passed as the username and the password MUST be empty.

In short, this means that all these curl commands will work:
- `curl -H 'Authorization: Bearer <token>' http://localhost:8080/rest/inbox`
- `curl -H 'X-OPENHAB-TOKEN: <token>' http://localhost:8080/rest/inbox`
- `curl -u '<token>[:]' http://localhost:8080/rest/inbox`
- `curl http://<token>@localhost:8080/rest/inbox`

2 REST API operations were adding to the AuthResource, to allow
authenticated users to list their tokens or remove (revoke) one.
Self-service for creating a token or changing the password is more
sensitive so these should be handled with a servlet and pages devoid
of any JavaScript instead of REST API calls, therefore for now they'll
have to be done with the console.

This also fixes regressions introduced with openhab#1713 - the operations
annotated with @RolesAllowed({ Role.USER }) only were not authorized
for administrators anymore.

* Generate a unique salt for each token

Reusing the password salt is bad practice, and changing the
password changes the salt as well which makes all tokens
invalid.

Put the salt in the same field as the hash (concatenated
with a separator) to avoid modifying the JSON DB schema.

* Fix API token authentication, make scope available to security context

The X-OPENHAB-TOKEN header now has priority over the Authorization
header to credentials, if both are set.

* Add self-service pages to change password & create new API token

Signed-off-by: Yannick Schaus <github@schaus.net>
GitOrigin-RevId: 8b52cab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement An enhancement or new feature of the Core REST/SSE
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Main UI] Admin user cannot change password
6 participants