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

graph: add appRoleAssignments and minimal application resource #5318

Merged
merged 20 commits into from
Jan 12, 2023

Conversation

butonic
Copy link
Member

@butonic butonic commented Jan 3, 2023

  • bump libregraph go dependency
  • add appRoleAssignment stub
  • add application stub
  • add positive test cases
  • add failure test cases
  • wire up settings service
  • make applicationid configurable

For now, I decided to intdroduce an application that is configured with GRAPH_APPLICATION_ID and GRAPH_APPLICATION_DISPLAYNAME. They are necessary to represent an application that owns the roles. In our case that is oCIS.

First question: is it only oCIS Web? I currently default to "oCIS Web".

Second question: the application id should be randomized on init. AFAICT it resembles the oc10 instance id. Although I'm not sure if it is the web client or the server side, see first question. The more I type the more I think it is the server side.

Third question: if we make the id configurable then we require clients to know the application id in advance. Should we implement listing applications at /applications? That way clients could discover the applications that are available. The WebUI could discover not only ocis web, but also onlyoffice and other applications. I strongly encourage everyone to check out the MS Graph Application Properties. It has things like a logo stream, endpoints for redirects of web applications, links to terms of service, support urls, ... If we entertain a publicly hosted oCIS web instance that could discover a graph endpoint using webfinger, similar to the openid connect discovery, being able to discover the application id, a displayname and a logo is desireable. Also ... I don't want to have to configure the wab ui to know the application id in advance. An in case the user management web ui (soon to become admin web ui) encounters multiple applications it will have to be able to deal with more than one application and multiple sets of appRoleIDs.

In light of the above, I propose to use just "oCIS" as the default application name, roll a uuid for the applcation id on ocis init and implement the ListApplications endpoint.

@kulmann @michaelstingl @felix-schwarz I just noticed how ms graph allows applications to register as a handler for certain filetypes:

Property Type Description
addIns addIn collection Defines custom behavior that a consuming service can use to call an app in specific contexts. For example, applications that can render file streams may set the addIns property for its "FileHandler" functionality. This will let services like Office 365 call the application in the context of a document the user is working on.

@update-docs
Copy link

update-docs bot commented Jan 3, 2023

Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would create a changelog item based on your changes.

@butonic butonic self-assigned this Jan 3, 2023
@ownclouders
Copy link
Contributor

ownclouders commented Jan 4, 2023

💥 Acceptance test localApiTests-apiSpacesShares-ocis failed. Further test are cancelled...

@butonic butonic force-pushed the approleassignments branch 2 times, most recently from 1df02b4 to 0341a7b Compare January 6, 2023 16:00
}}
}, nil)

r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$expand=appRoleAssignments", nil)
Copy link
Member Author

Choose a reason for hiding this comment

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

  • the /graph/v1.0/me/users URLs further above are all bonkers -> prepare a subsequent PR to correct them

@@ -74,7 +74,7 @@ var _ = Describe("EducationUsers", func() {
service.WithGatewayClient(gatewayClient),
service.EventsPublisher(&eventsPublisher),
service.WithIdentityEducationBackend(identityEducationBackend),
//service.WithRoleService(roleService),
service.WithRoleService(roleService),
Copy link
Member Author

@butonic butonic Jan 6, 2023

Choose a reason for hiding this comment

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

this now works because I actually use the Role service passed in service.go

	if options.RoleService == nil {
		svc.roleService = settingssvc.NewRoleService("com.owncloud.api.settings", grpc.DefaultClient())
	} else {
		svc.roleService = options.RoleService
	}

@@ -54,7 +54,7 @@ require (
github.com/onsi/ginkgo/v2 v2.5.0
github.com/onsi/gomega v1.24.1
github.com/orcaman/concurrent-map v1.0.0
github.com/owncloud/libre-graph-api-go v1.0.1
github.com/owncloud/libre-graph-api-go v1.0.2-0.20230105141655-9384face4d5d
Copy link
Member Author

Choose a reason for hiding this comment

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

  • bump to a release

@butonic
Copy link
Member Author

butonic commented Jan 10, 2023

@micbar @kulmann the permissions in msgraph have a technical layout: {resource}.{operation}.{constraint}

Value Description Examples
{resource} Refers to a Microsoft Graph resource to which the permission allows access. For example, the user resource. UserApplication, or Group
{operation} Refers to the Microsoft Graph API operations that are allowed on the data that's exposed by the resource. For example, Read for read operations only, or ReadWrite for read, create, update, and delete operations. ReadReadBasicReadWriteCreateManage, or Migrate
{constraint} Determines the potential extent of access an app will have within the directory. This value may not be explicitly declared. When undeclared, the default constraint is limited to data that's owned by the signed-in user. AllAppFolderOwnedBySelectedSharedHidden

I recommend skimming the MS Graph permissions reference to get a feeling of how granular they are.

Regarding appRoleAssignments I found this explanation that clarifies how the value property of an appRole is used:

When the app role which has been assigned to a principal has a non-empty value property, this will be included in the roles claim of tokens where the subject is the assigned principal (e.g. SAML responses, ID tokens, access tokens identifying a signed-in user, or an access token identifying a service principal). Applications and APIs use these claims as part of their authorization logic.

AFAICT there is no direct permission lookup mechanism. Instead, the naming pattern of the role value {resource}.{operation}.{constraint} determines the (set of) permissions:

  1. sharding all permissions by {resource}
  2. either granting only a subset of {operation}s, eg. Read or ReadWrite, specific permissions like ReadBasic, Create, Migrate or Manage. I assume that often one set of operations includes anoter, eg. Read includes ReadBasic and ReadWrite includes Read and hence ReadBasic.
  3. the optional {constraint} allows expanding the operation beyond resources owned by the user. eg All allows the user to perform that operation on all resorces.

As given in the table above ReadWrite actuall contains read, create, update, and delete operations

Our space managemer role should be Drive.Manage.All ... that is if Manage has the intended meaning ... so let's check ...

Well ... there are only Files permissions, and they come with examples:

  • Files.Read: Read files stored in the signed-in user's OneDrive (GET /me/drive/root/children)
  • Files.Read.All: Read files shared with the signed-in user (GET /me/drive/root/sharedWithMe)
  • Files.ReadWrite: Write a file in the signed-in user's OneDrive (PUT /me/drive/root/children/filename.txt/content)
  • Files.ReadWrite.All: Write a file shared with the user (PUT /users/rgregg@contoso.com/drive/root/children/file.txt/content)
  • Files.ReadWrite.AppFolder: Write files into the app's folder in OneDrive (PUT /me/drive/special/approot/children/file.txt/content)

To me it seems that they express specific permissions through a 'custom' constraint like AppFolder

Oh ... they actually have a list of Permission IDs, eg.:

Permission name Type ID
Files.ReadWrite.All Application 75359482-378d-4052-8f01-80520e7db3cd
Files.ReadWrite.All Delegated 863451e7-0667-486c-a5d6-d135439485f0

So what are delegated permissions?

Lets take the files delegated permissions:

Permission Display String Description Admin Consent Required Microsoft Account supported
Files.ReadWrite.All Have full access to all files user can access Allows the app to read, create, update, and delete all files the signed-in user can access. No Yes

vs the Files application permissions:

Permission Display String Description Admin Consent Required
Files.ReadWrite.All Read and write files in all site collections Allows the app to read, create, update, and delete all files in all site collections without a signed in user. Yes

Maybe it helps to clarify what delegated vs application permissions are:

  • Delegated permissions, also called scopes, are used in the delegated access scenario. They're permissions that allow the application to act on behalf of a signed-in user. However, the application will never be able to access anything the signed-in user couldn't access.
  • Application permissions, also called app roles, are used in the app-only access scenario, without a signed-in user present. The application will be able to access any data that the permission is associated with. For example, an application granted the Files.Read.All application permission will be able to read any file in the organization.

or in short:

  • delegated = on behalf off a user
  • application = without a user

But ... if I look at the comparison ... :

  Delegated permissions Application permissions
Types of apps Web app / Mobile / Single-page app (SPA) Web / Daemon
Access context Get access on behalf of a user Get access without a user
Who can consent Users can consent for their dataAdmins can consent for all users Only admin can consent
Other names ScopesOAuth2 permissions App rolesApp-only permissionsDirect access permissions
Result of consent oAuth2PermissionGrant appRoleAssignment

... access on behalf of users should be granted by an oAuth2PermissionGrant.

Its Properties have a scope property, which is literally the oauth2 scope:

Property Type Description
scope String A space-separated list of the claim values for delegated permissions which should be included in access tokens for the resource application (the API). For example, openid User.Read GroupMember.Read.All. Each claim value should match the value field of one of the delegated permissions defined by the API, listed in the oauth2PermissionScopes property of the resource service principal. Must not exceed 3850 characters in length.

and they can be listed at:

GET /me/oauth2PermissionGrants
GET /users/{id | userPrincipalName}/oauth2PermissionGrants

An interesting aside is Grant or revoke API permissions programmatically ... it also talks about granting delegated access to an application on behalf of all users in a tenant...

The /users endpoint does not seem to allow $expand because the grants come as the scope? ...

Thinking about our space managemer role, why is there no mention of an admin that can create additional drives? AFAICT because all groups implicitly have a drive, which means all classes get a drive ... but who can manage quota? ah yeah the msgraph does not expose an api for that ... duh

Ok, looking at the permissions reference, Manage contains create and delete operations, which makes our space managemer role really Drive.Manage.All, I would not use Files.Manage.All because that would contain ReadWrite, and Files.ReadWrite.All is a permissions that allows access to files. But we want to manage Drives aka spaces.

@butonic
Copy link
Member Author

butonic commented Jan 10, 2023

Trying to map our current roles to MS Graph permissions:

First a set of relevant permissions:

Permission Display String Description Admin Consent Required Microsoft Account supported
User.ReadWrite Read and write access to user profile Allows the app to read the signed-in user's full profile. It also allows the app to update the signed-in user's profile information on their behalf. No Yes
User.ReadBasic.All Read all users' basic profiles Allows the app to read a basic set of profile properties of other users in your organization on behalf of the signed-in user. This includes display name, first and last name, email address, open extensions and photo. Also allows the app to read the full profile of the signed-in user. No No
Files.ReadWrite.All Have full access to all files user can access Allows the app to read, create, update, and delete all files the signed-in user can access. No Yes
GroupMember.ReadWrite.All Read and write group memberships Allows the app to list groups, read basic properties, read and update the membership of the groups the signed-in user has access to. Group properties and owners cannot be updated and groups cannot be deleted. Yes No
Group.ReadWrite.All Read and write all groups Allows the app to create groups and read all group properties and memberships on behalf of the signed-in user. Also allows the app to read and write calendar, conversations, files, and other group content for all groups the signed-in user can access. Additionally allows group owners to manage their groups and allows group members to update group content. Yes No
User.ReadWrite.All Read and write all users' full profiles Allows the app to read and write the full set of profile properties, reports, and managers of other users in your organization, on behalf of the signed-in user. Also allows the app to create and delete users as well as reset user passwords on behalf of the signed-in user. Yes No
UnifiedGroupMember.Read.AsGuest Read unified (Microsoft 365) group memberships as a guest user Allows the app to read basic unified group properties, memberships, and owners of the group the signed-in guest is a member of. Yes No
Drive.Manage.All Read, Write all drive (aka spaces) properties in the organization, on behalf of the signed-in user. Also allows the app to create and delete drives on behalf of the signed-in user. Yes No
Permission Display String Description Admin Consent Required
AppRoleAssignment.ReadWrite.All Manage app permission grants and app role assignments Allows the app to manage permission grants for application permissions to any API (including Microsoft Graph) and application assignments for any app, on behalf of the signed-in user. Yes
DelegatedPermissionGrant.ReadWrite.All Manage delegated permission grants Allows the app to manage delegated permission grants for any API (including Microsoft Graph), on behalf of the signed-in user. Yes

Then our current user roles would be expressed as

oCIS role delegated MS Graph permissions Comment
User User.ReadWrite User.ReadBasic.All Files.ReadWrite.All GroupMember.ReadWrite.All This is the basic set of permissions to allow users access to files in personal as well as project drives / spaces
Admin User.ReadWrite User.ReadBasic.All Files.ReadWrite.All Group.ReadWrite.All User.ReadWrite.All Drives.Manage.All DelegatedPermissionGrant.ReadWrite.All Admins can in addition manage users, groups and drives/spaces and assign roles .. although I'm still not sure whether AppRoleAssignment or DelegatedPermissionGrant is the right thing
Space Admin User.ReadWrite User.ReadBasic.All Files.ReadWrite.All GroupMember.ReadWrite.All Drive.Manage.All space admins can aeccess their own files and manage all drives / spaces
Guest User.ReadWrite Files.ReadWrite.All UnifiedGroupMember.Read.AsGuest guests cannot list the basic user properties (so he cannot list users for internal sharing)?, they can read and write all files they have access to, and they can list which groups they are a member of

Taking the list of roles and permissions @mmattel came up with in https://github.com/owncloud/docs-ocis/pull/268/files

Permission Type Admin Space Admin User Guest
Role Management ReadWrite.All
Account Management ReadWrite.All
Group Management ReadWrite.All
Self Management ReadWrite.Own ReadWrite.Own
Settings Managment ReadWrite.All
Read-Write Language ReadWrite.All ReadWrite.Own ReadWrite.Own ReadWrite.Own
Set Space Quota ReadWrite.All ReadWrite.All
List All Spaces Read.All Read.All
Create Space ReadWrite.All Create.All
Delete Home Space Delete.All
Delete All Spaces Delete.All

This can be rewritten as (remember: no constraint = Own):

Resource Admin Space Admin User Guest comment
appRoleAssignment ReadWrite.All Read Read Read all users need to be able to list the roles of an app ..... hm
user ReadWrite.All ReadWrite ReadWrite ReadWrite this is for self management, only the admin can manage users
group ReadWrite.All only the admin can manage groups
groupMember Read.All Read.All normal users can see their groups and search for groups as share recipients
unifiedGroupMember Read.AsGuest guests need to be able to see which group they are a member of
settings management hm maybe profile properties ... that would be the user resource
read-write language hm also profile properties ... that would be the user resource
drive Manage.All ReadWrite.All Read Read only admins can create and delete (=Manage), space admins can set quota and update update spaces (eg. grant acces), user and guests can access the list of drives they have access to.

It seems the settings and read write language cannot be expressed that granular. They all fold into User.ReadWrite, which includes writing your own profile properties like preferredLanguage.

@butonic butonic marked this pull request as ready for review January 10, 2023 15:15
@butonic butonic force-pushed the approleassignments branch 2 times, most recently from 328f24b to 3b81f43 Compare January 11, 2023 14:26
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
…AppRoleAssignment, configurable app id and displayname

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
…ng and logging

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
@butonic butonic changed the title graph: add appRoleAssignments and application stubs graph: add appRoleAssignments and minimal application resource Jan 12, 2023
Copy link
Contributor

@micbar micbar left a comment

Choose a reason for hiding this comment

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

LGTM, performance needs to be looked at later.

Co-authored-by: Michael Barz <mbarz@owncloud.com>
@sonarcloud
Copy link

sonarcloud bot commented Jan 12, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 13 Code Smells

60.9% 60.9% Coverage
2.6% 2.6% Duplication

@butonic butonic merged commit 078698f into master Jan 12, 2023
@delete-merged-branch delete-merged-branch bot deleted the approleassignments branch January 12, 2023 15:09
ownclouders pushed a commit that referenced this pull request Jan 12, 2023
Author: Jörn Friedrich Dreyer <jfd@owncloud.com>
Date:   Thu Jan 12 16:09:34 2023 +0100

    graph: add appRoleAssignments and minimal application resource (#5318)

    * bump libregraph-go lib

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * add appRoleAssignment stubs

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * add get application stub

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * fetch appRoles for application from settings service

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * initial list appRoleAssignments implementation

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * initial create appRoleAssignment implementation, extract assignmentToAppRoleAssignment, configurable app id and displayname

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * initial delete appRoleAssignment implementation, changed error handling and logging

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * initial expand appRoleAssignment on users

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * test user expand appRoleAssignment

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * test appRoleAssignment

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * fix education test by actually using the mocked roleManager

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * test getapplication

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * list assignments

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * use common not exists error handling

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * default to just 'ownCloud Infinite Scale' as application name

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * fix store_test

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * roll application uuid on init

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * fix tests

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * extract method

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

    * Apply suggestions from code review

    Co-authored-by: Michael Barz <mbarz@owncloud.com>

    Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
    Co-authored-by: Michael Barz <mbarz@owncloud.com>

// Application defines the available graph application configuration.
type Application struct {
ID string `yaml:"id" env:"GRAPH_APPLICATION_ID" desc:"The ocis application id shown in the graph. All app roles are tied to this."`
Copy link
Contributor

Choose a reason for hiding this comment

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

@butonic could you add a changelog item here? Just pulled the latest oCIS docker image and had it fail with an error until I added the GRAPH_APPLICATION_ID ;)

Copy link
Contributor

Choose a reason for hiding this comment

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

AKA "I went to the changelog first, didn't find anything obvious and ended up here in your PR"

Copy link
Member Author

Choose a reason for hiding this comment

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

sorry, yes ... I hope you got at least a meaningful error message?

Unfortunately, rolling a random application id also is also suboptimal because we would have to write it to the yaml config, which might be readonly ... I'm thinking about a solution for that. maybe store it in the metadata instead of the yaml file ...

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.

4 participants