Using the Node.js SDK, I've created a user and a resource group. How can I now assign the user to the resource group as an owner?
You could refer to this example.
authzClient.roleAssignments.create(scope, assignmentGuid, roleCreateParams, function (err, roleAssignment, req, res) {
if (err) {
console.log('\nError occured while creating the roleAssignment: \n' + util.inspect(err, { depth: null }));
return;
}
According to your need, you need the example.
Owner role id is 8e3af657-a8ff-443c-a75c-2fe8c4bcb635.
scope should be like this /subscriptions/{subscription-id}/resourceGroups/myresourcegroup1.
Replace application id to your user object id.
Also, you could use Azure rest API to do this, please refer to this link.
Related
Using a modified version of the Microsoft MSAL quickstart for node.js (original here), I successfully received an access token for the Azure Storage API using the implicit flow. The token included a groups claim, but one of the GUIDs in the claim does not seem to correlate with any group in the tenant. After removing the user from every group, the claim still contains that GUID (and as expected no others anymore):
"groups": [
"2cb3a5e8-4606-4407-9a97-616246393b5d"
],
A Google search for that GUID didn't result in any hits, so I'm assuming it is not a well-known GUID of some sort.
Why do I get this "unknown" GUID in a group claim?
The AAD tenant involved is a very small tenant, exclusively used by me for learning AAD and authentication. As such, it only contains a single group. The user involved is not a member of this single group.
I've looked at user page in the Azure Portal, which indeed shows that the user is "not a member of any groups". Azure CLI also show that the user isn't a member of any group:
$ az ad user get-member-groups --upn jurjen#stupendous.org
[]
$
The full list of groups in this tenant contains just a single group, and as you can see its ObjectID does not match the GUID I get in the claim:
$ az ad group list --query [].objectId --output tsv
b1cc46de-8ce9-4395-9c7c-e4e90b3c0036
$
I've also created another application registration and have it expose a dummy API. When using that dummy API as scope I again successfully receive
an access token, but this one again includes the same unknown GUID as the single group claim.
Here are the hopefully relevant bits of the code.
As mentioned above, first I retrieved an access token for Azure Storage:
var requestObj = {
scopes: ["https://storage.azure.com/user_impersonation"]
};
... but I get the exact same result with a dummy API:
var requestObj = {
scopes: ["api://7c7f72e9-d63e-44b6-badb-dd0e43df4cb1/user_impersonation"]
};
This bit logs the user in:
function signIn() {
myMSALObj.loginPopup(requestObj).then(function (loginResponse) {
//Successful login
showWelcomeMessage();
acquireTokenPopup();
}).catch(function (error) {
//Please check the console for errors
console.log(error);
});
}
The token is acquired here. I'm aware that callMSGraph won't work here given the scope of the token. I get the token from the browser console log and decode it using jwt.ms.
function acquireTokenPopup() {
//Always start with acquireTokenSilent to obtain a token in the signed in user from cache
myMSALObj.acquireTokenSilent(requestObj).then(function (tokenResponse) {
console.log("Access Token from cache: " + JSON.stringify(tokenResponse.accessToken));
callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
}).catch(function (error) {
console.log(error);
// Upon acquireTokenSilent failure (due to consent or interaction or login required ONLY)
// Call acquireTokenPopup(popup window)
if (requiresInteraction(error.errorCode)) {
myMSALObj.acquireTokenPopup(requestObj).then(function (tokenResponse) {
console.log("Access Token after interaction: " + JSON.stringify(tokenResponse.accessToken));
callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
}).catch(function (error) {
console.log(error);
});
}
});
}
You will also get directoryRole ids in the groups(got from the access token). You can request https://graph.microsoft.com/v1.0/me/memberOf to check the details. Here is the graph explorer.
I'm trying to use a Service Principle to access a Batch pool from an Azure Function and running into authentication issues that I don't understand. The initial login with the Service Principle works fine, but then using the credentials to access the batch pool returns a 401.
Below is a condensed version of my code with comments at the key points
module.exports.dispatch = function (context) {
MsRest.loginWithServicePrincipalSecret('AppId', 'Secret', 'TennantId', function(err, credentials){
if (err) throw err;
// This works as it prints the credentials
context.log(credentials);
var batch_client = new batch.ServiceClient(credentials, accountUrl);
batch_client.pool.get('mycluster', function(error, result){
if(error === null)
{
context.log('Accessed pool');
context.log(result);
}
else
{
//Request to batch service returns a 401
if(error.statusCode === 404)
{
context.log('Pool not found yet returned 404...');
}
else
{
context.log('Error occurred while retrieving pool data');
context.log(error);
}
//'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly.
context.res = { body: error.body.message.value };
context.done();
}
});
});
};
How can the initial login with a service principle work no problem, but then the credentials it returns not be able to access the batch pool?
The actual error says to check the auth header on the request, which I can see and the Authorisation header isn't even present.
I've triple checked the Active Directory access control for the batch account the App ID and secret are the ones belonging to the owner of the batch account. Any ideas what to try next?
The credentials expected by the Azure Batch npm client aren't the Azure Active Directory credentials/token, but the keys for the batch account. You can list your keys using the Azure CLI with a command like the following:
az batch account keys list -g "<resource-group-name>" -n "<batch-account-name>"
sample here
Then you can create the credentials parameter with those keys:
var credentials = new batch.SharedKeyCredentials('your-account-name', 'your-account-key');
You could still involve a Service Principal here if you wanted to store your batch keys in something like Key Vault, but then your code would be:
Get Service Principal auth against key vault to fetch name and key
Use name and key to create credentials
You cannot use the same OAuth token returned from the Azure Resource Management endpoint with Batch. Assuming your service principal has the correct RBAC permissions, auth with the Azure Batch endpoint: https://batch.core.windows.net/ instead (assuming you are using Public Azure).
You do not need to get the shared key credentials for the Batch account, credentials via AAD should be used instead if you are using an AAD service principal.
I happened to run across this same issue and I didn't have the option of using SharedKeyCredentials so I wanted to share my solution in case anyone else finds it helpful.
As fpark mentions, we need to get an OAuth token to use with Batch instead of the default Azure Resource Management. Below is the original code posted by Mark with the minor modification needed to make it work with Batch:
module.exports.dispatch = function (context) {
let authOptions = {tokenAudience: 'batch'};
MsRest.loginWithServicePrincipalSecret('AppId', 'Secret', 'TennantId', authOptions, function(err, credentials){
if (err) throw err;
// This works as it prints the credentials
context.log(credentials);
var batch_client = new batch.ServiceClient(credentials, accountUrl);
batch_client.pool.get('mycluster', function(error, result){
if(error === null)
{
context.log('Accessed pool');
context.log(result);
}
else
{
//Request to batch service returns a 401
if(error.statusCode === 404)
{
context.log('Pool not found yet returned 404...');
}
else
{
context.log('Error occurred while retrieving pool data');
context.log(error);
}
//'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly.
context.res = { body: error.body.message.value };
context.done();
}
});
});
};
Am using the node.js SDK to call the Azure notification hub api, I registered an application using Azure Active directory also i created a resource group and added my user to it as owner. But am getting this error when calling the api:
AuthorizationFailed
The client '333848ca-f996-XXXXXXXXXXXX' with object id
'333848ca-f996-XXXXXXXXX' does not have authorization to perform
action 'Microsoft.NotificationHubs/namespaces/write' over scope
'/subscriptions/ef8e8e-XXXXXXXXXXXX/resourceGroups/notificationGroup/providers/Microsoft.NotificationHubs/namespaces/namespaceapp1
The code:
MsRest.loginWithServicePrincipalSecret(
My Subscription ID,
My Application Secret Key,
My Directory ID, // found under AD -> properties
(err, credentials) => {
if (err) throw err;
// eslint-disable-next-line
const client = new notificationHubsClient(credentials, AZURE_SUBSCRIPTION_ID);
const createNamespaceParameters = {
location: AZURE_LOCATION,
tags: {
tag1: 'value1',
tag2: 'value2',
},
};
client.namespaces.createOrUpdate(AZURE_RESOURCE_GROUP_NAME, req.body.name, createNamespaceParameters,
(error, result) => {
if (error) {
res.send(error);
}
console.info(`Namespace created${JSON.stringify(result, null, ' ')}`);
res.send('app created');
});
},
);
According to the error message, you'll need to add your Azure Active directory application to the Contributor role for creating the namespace in Azure Notification Hub.
You can follow the steps below which are also documented here.
Go to Azure portal and select Subscriptions.
Select the particular subscription (resource group or resource) to assign the application to.
Select Access Control (IAM).
Select Add.
Select the Contributor role to assign to your AAD application.
Consider the following scenario:
There's a collection called Resources and a special user can CRUD it on an admin site.
When a regular user accesses the site, the routes are created dynamically in the browser depending on a subscription to Resources.
We would like to pause the router lookup until the subscription's callback function terminates. Otherwise the user is presented a "404" when accessing /<dynamically-created-route> directly.
Is there an easy way to do this?
Something like this? You create a dynamic route based on a resource id, waitOn a subscription with the id value, and then route based on whether or not you can access the resource with the id. This assumes you have the proper publishing setup in your server code also.
Resources = new Mongo.Collection("resources");
Router.route("/resources/:_id", {
name: "resources",
waitOn: function() {
// Waits on the subscription
return Meteor.subscribe("resources", this.params._id);
},
action: function() {
// Should only return the resource you subscribed to unless you
// subscribe to resources in another part of your application
var resource = Resources.findOne({});
if (resource) {
// The resource exists and the user is able to access it.
this.render("resourceTemplate", {
data: {
resource: resource
}
});
}
else {
Router.go("/404");
}
}
});
A REST API is written in ExpressJs 4.x.x / NodeJS.
Let's assume an interface :
app.delete('/api/v1/users/:uid', function (req, res, next) {
...
}
So with that interface users can be deleted.
Let's assume there are 2 Customers in the system, and each Customer has Users. A User can have the privilege of deleting other Users with a role named CustomersAdmin.
But this User should only be able to delete Users which are Users from his Company(Customer).
So, let's get ACL into the scene. Assuming in our ACL we can define roles, resources and permissions. (Code is adopted from http://github.com/OptimalBits/node_acl#middlware.)
app.delete('/api/v1/users/:uid', acl.protect(), function (req, res, next)
{
// ? Delete User with uid = uid or check
// ? first if current user is in same company as user uid
}
There are two things to consider. One is protecting the route from persons without permission to HTTP/DELETE on that route ( /api/v1/users/:uid ) and the other is that a Person with Role CustomersAdmin shall not be allowed to delete Users from another Customer.
Is ACL supposed to do both? Or is it supposed to protect the route /api/v1/users?
So, would I use it like
acl.allow([
{
roles:'CustomersAdmin',
allows:[
{resources:['/api/v1/users', '/api/v1/users'] permissions:'delete'}
}
app.delete('/api/v1/users/:uid',acl.middleware(3), function(req,res,next)
{
Make sure uid is a User from same Customer as request is from(req.session.userid)
}
This would allow every User with Role CustomersAdmin to delete whatever user he wants.
Or is it preferable to define each possible Users route as a Resource and define multiple Roles which can interact with them?
acl.allow([
{
roles:'CustomersAdminOne',
allows:[
{resources:['/api/v1/users/1', '/api/v1/users/2'], permissions:'delete'}]
},
{
roles:'CustomersTwoAdmin',
allows:[
{resources:['/api/v1/users/3','/api/v1/users/4'], permissions:'delete'}
]
}
app.delete('/api/v1/users/:uid',acl.middleware(), function(req,res,next)
{
no logic needed to be sure that request is from a user within the same customer
}
The way I solved this was to create a role per user. I use a mongoose post save hook:
acl.addUserRole(user._id, ['user', user._id]);
Then in a post save hook for a resource I do this:
acl.allow(['admin', doc.user._id], '/album/' + doc._id, ['*']);
acl.allow(['guest', 'user'], '/album/' + doc._id, ['get']);
You can then use the isAllowed method to check if req.user has the right permissions.