Related
When I make requests to the Microsoft Graph API's invite endpoint to create secure (email-based) sharing links for a specific collection of emails that are external to our organization, the response does not contain the requested sharing link for certain email addresses. When I look at the SharePoint site UI to see how the permissions are listed for the item, the email address with issue (RealEmail) shows up differently from the one that does return a sharing link through the API (TestEmail). Both RealEmail and TestEmail are marked as external users, but it seems that the RealEmail external user is recognized as an actual external or guest user account, rather than just being an email unknown to SharePoint, like TestEmail.
Here's the request I made on Microsoft Graph Explorer:
POST https://graph.microsoft.com/v1.0/sites/{site-id}/drives/{drive-id}/items/{item-id}/invite
{
"requireSignIn": true,
"sendInvitation": false,
"roles": [
"read"
],
"recipients": [
{
"email": "RealEmail#example.com"
},
{
"email": "TestEmail#example.com"
}
]
}
And this was the response (with real data removed):
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(permission)",
"value": [
{
"#odata.type": "#microsoft.graph.permission",
"id": "removed",
"roles": [
"read"
],
"grantedTo": {
"user": {
"email": "RealEmail#example.com",
"id": "removed",
"displayName": "RealFirstName RealLastName"
}
}
},
{
"#odata.type": "#microsoft.graph.permission",
"roles": [
"read"
],
"grantedToIdentities": [
{
"user": {
"email": "TestEmail#example.com"
}
}
],
"invitation": {
"signInRequired": true
},
"link": {
"type": "view",
"webUrl": "removed"
}
}
]
}
This is what the Manage Access UI looks like (with real data removed)
I'm trying to fetch the role collections assigned to my user in CAP (NodeJS). I've assigned my user to one role collections, but it does not get reflected upon testing (BAS). I've attached my configuration and statement I use to fetch the role collections. Any clues what I'm missing?
xs-security.json
{
"xsappname": "test-cap",
"tenant-mode": "dedicated",
"description": "Security profile of called application",
"scopes": [
{
"name": "uaa.user",
"description": "UAA"
},
{
"name": "$XSAPPNAME.Admin",
"description": "Admin"
}
],
"role-templates": [
{
"name": "Token_Exchange",
"description": "UAA",
"scope-references": [
"uaa.user"
]
},
{
"name": "Admin",
"description": "Admin Role",
"scope-references": [
"$XSAPPNAME.Admin"
]
}
],
"role-collections": [
{
"name": "Admin",
"description": "Admin",
"role-template-references": [
"$XSAPPNAME.Admin"
]
}
],
"oauth2-configuration": {
"redirect-uris": [
"https://*.applicationstudio.cloud.sap/**"
]
}}
package.json
"cds": {
"build": {
"target": "."
},
"requires": {
"uaa": {
"kind": "xsuaa"
},
"db": {
"kind": "hana"
}
},
"hana": {
"deploy-format": "hdbtable"
}}
mta.yaml
Output of console.log(req.user)
{
"id": "matthijs#dummy.com",
"_roles": {
"any": 1,
"identified-user": 1,
"authenticated-user": 1
},
"attr": {},
"tenant": null}
As far as I know the roles are stored in the JWT token, not in the CDS user object itself. The user object itself only "stores" the pseudo-roles (see here: https://cap.cloud.sap/docs/guides/authorization#roles)
However you can check for specific roles using the req.user.is(<rolename>) method. (see: https://cap.cloud.sap/docs/node.js/authentication#enforcement)
I have the Azure AD users that are synced from my on-premises AD.
When I updated the user's properties (such as displayName, department, and so on), some users succeeded in updating, but some users failed to update.
In Azure AD, the audit logs of the update successful user and the failed user are as follows.
////////////////// update success user
{
"id": "Directory_xxxxxxx_112926480",
"category": "UserManagement",
"correlationId": "xxxxx",
"result": "success",
"resultReason": "",
"activityDisplayName": "Update user",
"activityDateTime": "2022-02-07T07:03:44.6467812Z",
"loggedByService": "Core Directory",
"operationType": "Update",
"initiatedBy": {
"user": null,
"app": {
"appId": null,
"displayName": "xxxx",
"servicePrincipalId": "xxxx",
"servicePrincipalName": null
}
},
"targetResources": [
{
"id": "xxxx",
"displayName": null,
"type": "User",
"userPrincipalName": "xxxx#xxxx",
"groupType": null,
"modifiedProperties": [
{
"displayName": "Included Updated Properties",
"oldValue": null,
"newValue": "\"\""
},
{
"displayName": "TargetId.UserType",
"oldValue": null,
"newValue": "\"Member\""
}
]
}
],
"additionalDetails": [
{
"key": "UserType",
"value": "Member"
},
{
"key": "User-Agent",
"value": "Apache CXF 3.2.14"
}
]
},
////////////////// update fail user
{
"id": "Directory_xxxx_118537500",
"category": "UserManagement",
"correlationId": "xxxx",
"result": "failure",
"resultReason": "Microsoft.Online.Workflows.PropertyUpdateNotAllowedException",
"activityDisplayName": "Update user",
"activityDateTime": "2022-02-07T07:03:04.9716261Z",
"loggedByService": "Core Directory",
"operationType": "Update",
"initiatedBy": {
"user": null,
"app": {
"appId": null,
"displayName": "xxxx",
"servicePrincipalId": "xxxx",
"servicePrincipalName": null
}
},
"targetResources": [
{
"id": "xxxx",
"displayName": null,
"type": "User",
"userPrincipalName": "xxxx#xxx",
"groupType": null,
"modifiedProperties": [
{
"displayName": "MethodExecutionResult.",
"oldValue": null,
"newValue": "\"Microsoft.Online.Workflows.PropertyUpdateNotAllowedException\""
},
{
"displayName": "TargetId.UserType",
"oldValue": null,
"newValue": "\"Member\""
}
]
}
],
"additionalDetails": [
{
"key": "UserType",
"value": "Member"
},
{
"key": "User-Agent",
"value": "Apache CXF 3.2.14"
}
]
},
Could you tell why the results are so different?
Any help would be appreciated.
Some user details are failed to update because of the error:
Microsoft.Online.Workflows.PropertyUpdateNotAllowedException
The above error usually occurs for many reasons like:
There may be some attributes that violate formatting requirements which restrict characters and character length of attribute values.
The attributes that require unique values may have duplicate attribute values in existing user account (on-prem AD).
May be the user principal name (UPN) was changed after the initial synchronization and must be updated manually.
Some attributes may match exclusion rules for directory synchronization.
The domain value that's used by AD DS attributes hasn't been verified.
To resolve this error, please check the below workarounds if they are helpful:
Use the IdFix DirSync Error Remediation Tool to check for duplicates, missing attributes, and rule violations.
Update AD DS attributes to remove duplicates, rule violations, and scoping exclusions.
Make sure the user properties can’t contain accent characters and the size of displayName, department etc… are under the maximum limits.
For more information in detail, go through below references.
References:
One or more objects don't sync when the Azure Active Directory Sync tool is used - Active Directory | Microsoft Docs
Troubleshoot directory synchronization errors with event 6941 - Office 365 | Microsoft Docs
I am implementing Keycloak authorization to my Node.js application. I created a realm and a client inside of it. I don't have any roles either in realm or in client. I have a route "/test" which is protected with Keycloak.
My keycloak.json looks like this:
{
"realm": "demo",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "myapp",
"public-client": true,
"confidential-port": 0
}
Define roles (realm roles or client roles), assign the roles to your user (user which you use to perform the test), check your role scope mappings (or you can configure your client as full-scope for testing purpouse), and check that you are protecting your '/test' route with those same permissions.
E.g. you can try with keycloak-nodejs adapter (example).
Look at this configuration file (keycloak realm configuration example):
{
"realm": "nodejs-example",
"enabled": true,
"sslRequired": "external",
"registrationAllowed": true,
"privateKey": "...................",
"publicKey": ".....................",
"requiredCredentials": [ "password" ],
"users" : [
{
"username" : "user",
"enabled": true,
"email" : "sample-user#nodejs-example",
"firstName": "Sample",
"lastName": "User",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": [ "user" ],
"clientRoles": {
"account": ["view-profile", "manage-account"]
}
}
],
"roles" : {
"realm" : [
{
"name": "user",
"description": "User privileges"
},
{
"name": "admin",
"description": "Administrator privileges"
}
]
},
"scopeMappings": [
{
"client": "nodejs-connect",
"roles": ["user"]
}
],
"clients": [
{
"clientId": "nodejs-connect",
"enabled": true,
"publicClient": true,
"baseUrl": "/",
"adminUrl" : "http://localhost:3000/",
"baseUrl" : "http://localhost:3000/",
"redirectUris": [
"http://localhost:3000/*"
],
"webOrigins": []
},
{
"clientId": "nodejs-apiserver",
"enabled": true,
"secret": "secret",
"redirectUris": [
"http://localhost:3000/*"
],
"webOrigins": [
"http://localhost:3000/*"
],
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"authorizationSettings": {
"resources": [
{
"name": "resource",
"type": "urn:nodejs-apiserver:resources:default",
"ownerManagedAccess": false,
"uris": [
"/*"
],
"scopes": [
{
"name": "view"
},
{
"name": "write"
}
]
}
],
"policies": [
{
"name": "Default Policy",
"description": "A policy that grants access only for users within this realm",
"type": "js",
"config": {
"code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n"
}
},
{
"name": "Default Permission",
"description": "A permission that applies to the default resource type",
"type": "resource",
"config": {
"defaultResourceType": "urn:nodejs-apiserver:resources:default",
"applyPolicies": "[\"Default Policy\"]"
}
}
]
}
}
]
}
Explanation:
From this sample, look at how a user has roles assigned (realm roles: "user", account client roles: "account": ["view-profile", "manage-account"]):
{
...
"users" : [
{
"username" : "user",
"enabled": true,
"email" : "sample-user#nodejs-example",
"firstName": "Sample",
"lastName": "User",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": [ "user" ],
"clientRoles": {
"account": ["view-profile", "manage-account"]
}
}
],
...
}
Look at how realm Roles are defined:
{
...
"roles" : {
"realm" : [
{
"name": "user",
"description": "User privileges"
},
{
"name": "admin",
"description": "Administrator privileges"
}
]
},
...
}
Look at how this sample uses a 'scope mapping' to map roles from realm to a user authenticated by a client (read more about this here role scope mapping):
{
...
"scopeMappings": [
{
"client": "nodejs-connect",
"roles": ["user"]
}
],
...
}
Look at how clients are defined. Check that 'nodejs-connect' client is public and 'nodejs-apiserver' is secret. In this sample, server is using the 'Authorization Api' to protect resources but you could protect your resources by granted roles only (if you want).
{
...
"clients": [
{
"clientId": "nodejs-connect",
"enabled": true,
"publicClient": true,
"baseUrl": "/",
"adminUrl" : "http://localhost:3000/",
"baseUrl" : "http://localhost:3000/",
"redirectUris": [
"http://localhost:3000/*"
],
"webOrigins": []
},
{
"clientId": "nodejs-apiserver",
"enabled": true,
"secret": "secret",
"redirectUris": [
"http://localhost:3000/*"
],
"webOrigins": [
"http://localhost:3000/*"
],
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"authorizationSettings": {
"resources": [
{
"name": "resource",
"type": "urn:nodejs-apiserver:resources:default",
"ownerManagedAccess": false,
"uris": [
"/*"
],
"scopes": [
{
"name": "view"
},
{
"name": "write"
}
]
}
],
"policies": [
{
"name": "Default Policy",
"description": "A policy that grants access only for users within this realm",
"type": "js",
"config": {
"code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n"
}
},
{
"name": "Default Permission",
"description": "A permission that applies to the default resource type",
"type": "resource",
"config": {
"defaultResourceType": "urn:nodejs-apiserver:resources:default",
"applyPolicies": "[\"Default Policy\"]"
}
}
]
}
}
]
...
}
Finally, look at javascript (index.js) file to see how it uses 'keycloak-connect' (adapter) to protect and enforce access policies (usage of Authorization Api).
Tip
In development, you can get an access token and use this page to decode and see the token's content.
JWT.IO
I hope this helps.
I also had a 403 - Access Denied. What helped was stepping through the auth code of keycloak-connect. The token validation happens in the function validateToken in node_modules\keycloak-connect\middleware\auth-utils\grant-manager.js
if (!token) {
reject(new Error('invalid token (missing)'))
} else if (token.isExpired()) {
reject(new Error('invalid token (expired)'))
} else if (!token.signed) {
reject(new Error('invalid token (not signed)'))
} else if (token.content.typ !== expectedType) {
reject(new Error('invalid token (wrong type)'))
} else if (token.content.iat < this.notBefore) {
reject(new Error('invalid token (stale token)'))
} else if (token.content.iss !== this.realmUrl) {
reject(new Error('invalid token (wrong ISS)'))
}
...
For me, the problem was that I configured the url to the keycloak server without the schema (https). token.content.iss includes the schema and so it rejected because of token.content.iss !== this.realmUrl.
Unfortunately they seem to swallow those valuable Error messages without logging or returning them. I opened a question regarding this behavior.
Is there any way to create a Calendar entry using a mailfile where the Organizer is not the mailfile Owner?
For example:
http://mycompanycom/mail/utils.nsf/api/calendar/events
I want to use a gereric mailfile where DAS is enabled and make all request against it; otherwise I'll need to query every Organizer's mailfile.
The only way I can make it to work is when I set the organizer the same as the mailfile owner, for example:
http://mycompany.com/mail1/ndev1.nsf/api/calendar/events
Here Notes Dev1 is the Owner for ndev1.nsf
{
"events": [
{
"summary": "TEST FEB 2018",
"location": "TEST LOCATION - NOT NEEDED",
"description": "",
"CalendarDateTime": {
"date": "2018-02-26",
"time": "17:00:00",
"utc": true
},
"start": {
"date": "2018-02-26",
"time": "17:00:00",
"utc": true
},
"end": {
"date": "2018-02-26",
"time": "18:00:00",
"utc": true
},
"class": "public",
"transparency": "opaque",
"sequence": 0,
"x-lotus-noticetype": "I",
"attendees": [
{
"role": "chair",
"status": "accepted",
"rsvp": false,
"displayName": "Notes Dev1/MYCOMPANY",
"email": "notes.dev1#mycompany.com"
},
{
"role": "req-participant",
"status": "needs-action",
"rsvp": true,
"displayName": "Pablo Solano/MYCOMPANY",
"email": "pablo.solano#mycompany.com"
},
{
"role": "req-participant",
"status": "needs-action",
"rsvp": true,
"displayName": "Notes Dev2/MYCOMPANY",
"email": "notes.dev2#mycomany.com"
},
{
"role": "req-participant",
"userType": "room",
"status": "accepted",
"rsvp": true,
"email": "maar#teradyne.com"
}
],
"organizer": {
"displayName": "Notes Dev1/MYCOMPANY",
"email": "notes.dev1#mycompany.com"
}
}
]
}
I found this url: http://www-10.lotus.com/ldd/ndseforum.nsf/xpTopicThread.xsp?documentId=CB20A0E36EE82AB385258154003B0A86
with this text:
One important caveat: The organizer property must match the owner of the database as specified in the request URL (/{database}/api/calendar/events).
I see few options for you:
Quick&dirty - change/add current user as owner of the calendar on the fly every time. Not recommended.
Make your code to work on behalf of universal identity, which is also owner of the calendar. Caveat: meeting will be arranged by that identity.
Create the meeting from user's calendar. Probably that's not what you want.