Impersonate google user with a service account - impersonation

I'm using google-api-php-client 0.6.1 and I'd like to know is there a way to impersonate concrete user with service account? My application needs to store some files in its google drive. So, I've decided to user service account and .p12 key - authentification. It works great, but all files are being stored in Service account, so I can't manage them. I'd like documents to be stored at the certain account (which was using to create the api project and the service account itself). I was trying to use this code:
$KEY_FILE = <p12 key file path>;
$key = file_get_contents($KEY_FILE);
$auth = new Google_AssertionCredentials(
$SERVICE_ACCOUNT_NAME,
array('https://www.googleapis.com/auth/drive'),
$key);
$auth->prn = '<certainuser#gmail.com>';
$client = new Google_Client();
$client->setUseObjects(true);
$client->setAssertionCredentials($auth);
return new Google_DriveService($client);
but I got "Error refreshing the OAuth2 token, message: '{ "error" : "access_denied" }'"

Don't user $auth->prn, use $auth->sub. This works for me:
// Create a new google client. We need this for all API access.
$client = new Google_Client();
$client->setApplicationName("Google Group Test");
$client_id = '...';
$service_account_name = '...';
$key_file_location = '...';
if (isset($_SESSION['service_token'])) {
$client->setAccessToken($_SESSION['service_token']);
}
$key = file_get_contents($key_file_location);
// https://www.googleapis.com/auth/admin.directory.group,
// https://www.googleapis.com/auth/admin.directory.group.readonly,
// https://www.googleapis.com/auth/admin.directory.group.member,
// https://www.googleapis.com/auth/admin.directory.group.member.readonly,
// https://www.googleapis.com/auth/apps.groups.settings,
// https://www.googleapis.com/auth/books
$cred = new Google_Auth_AssertionCredentials(
$service_account_name,
array(
Google_Service_Groupssettings::APPS_GROUPS_SETTINGS,
Google_Service_Directory::ADMIN_DIRECTORY_GROUP,
Google_Service_Directory::ADMIN_DIRECTORY_GROUP_READONLY,
Google_Service_Directory::ADMIN_DIRECTORY_GROUP_MEMBER,
Google_Service_Directory::ADMIN_DIRECTORY_GROUP_MEMBER_READONLY,
Google_Service_Books::BOOKS,
),
$key,
'notasecret'
);
//
// Very important step: the service account must also declare the
// identity (via email address) of a user with admin priviledges that
// it would like to masquerade as.
//
// See: http://stackoverflow.com/questions/22772725/trouble-making-authenticated-calls-to-google-api-via-oauth
//
$cred->sub = '...';
$client->setAssertionCredentials($cred);
if ($client->getAuth()->isAccessTokenExpired()) {
$client->getAuth()->refreshTokenWithAssertion($cred);
}
$_SESSION['service_token'] = $client->getAccessToken();

Related

Keyvault Authentication using username and password

The customer has created a key vault and store the credentials . To authenticate key vault , I have created the application in the node and using client id and client secret , I am able to read the secrets. But now the customer wants not to use the client id and client secret , instead use the username and password of the AZURE to access the keyvault in the program. Its one dedicated user for the keyvault access with no MFA.
I am not sure if we can access the keyvault with username and password from the node js. Kindly suggest.
Thanks
For this requirement, I also think that use username-password flow is unnecessary and client credential flow should be better (as juunas mentioned in comments). But if the customer still want to use username-password flow to implement, I can provide a sample as below for your reference:
1. You should register an app in AD with native platform but not web platform.
And please check if "Treat application as a public client" is enabled.
If your app is web platform, when you run the node js code it will show error message to ask you provide "client secret" even if you use username-password flow.
2. You need to add the azure key vault permission to your app.
And do not forget grant admin consent for it.
3. Then you can refer to the code below to get the secret value.
const KeyVault = require('azure-keyvault');
const { AuthenticationContext } = require('adal-node');
const clientId = '<clientId>';
const username = '<username>';
const password = '<password>';
var secretAuthenticator = function (challenge, callback) {
var context = new AuthenticationContext(challenge.authorization);
return context.acquireTokenWithUsernamePassword(challenge.resource, username, password, clientId, function(
err,
tokenResponse,
) {
if (err) throw err;
var authorizationValue = tokenResponse.tokenType + ' ' + tokenResponse.accessToken;
return callback(null, authorizationValue);
});
};
var credentials = new KeyVault.KeyVaultCredentials(secretAuthenticator);
var client = new KeyVault.KeyVaultClient(credentials);
client.getSecret("https://<keyvaultname>.vault.azure.net/", "<secret name>", "<secret version>", function (err, result) {
if (err) throw err;
console.log("secret value is: " + result.value);
});

Authorization for azure app resource management without client ID and client secret

I'm trying to create an application where the user can manage their Azure resources (sql databases, storage). How can I authorize the user without needing their client ID and client secret.
I've looked at their documentation: https://godoc.org/github.com/Azure/go-autorest/autorest/azure/auth, however all of them requires environment variables and/or clientID/clientSecret. Is there a way where a user can provide username/password and I can get back an authorizer
type Client struct {
ServersClient postgresql.ServersClient
}
func NewCloudClient() *Client {
return &Client{}
}
func (c *Client) Init(config map[string]string) error {
var (
subscriptionID = config["subscriptionID"]
// tenantID = config["tenantID"]
// clientID = config["clientID"]
// clientSecret = config["clientSecret"]
// resourceGroup = config["resourceGroup"]
)
// oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, tenantID)
// if err != nil {
// return errors.Wrap(err, "error getting OAuthConfig")
// }
// spt, err := adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, resourceGroup)
serversClient := postgresql.NewServersClient(subscriptionID)
//serversClient.BaseClient.Authorizer = autorest.NewBearerAuthorizer(spt)
c.ServersClient = serversClient
return nil
}
In all cases of authorization flow you will need at least client id. In some you will need more like client secret.
You can read about OAuth authorization here https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-app-types
but in general what you are aiming for is either of two cases
Authorize as user without internaction using username and password (this would be client resource owner flow
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
Authorize as user with his interaction
if this is code that can't get postback URL then use device code flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code
or for anything in the browser use standard implicit grant flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow
You will quickly notice that in all cases you NEED to have client ID. This is because how Azure Authentication works.

Set display name / username for email local accounts in Azure AD B2C?

I'm new to Azure AD B2C and have set up a site that correctly authenticates using local accounts (email only). When the validation request comes back, I see the email address under the 'emails' claim, but the 'name' claim comes back as 'unknown'.
Looking in Azure portal, the account is created but the name is unset and is 'unknown' for all users that register. This isn't what I was expecting. I would prefer that the 'name' be set to the email address by default so that it is easier to find the account in the portal, since we aren't collecting a 'Display Name' at all for this account type (user already enters given and surname).
Do I have something configured incorrectly, and is there a way to default the username to the email address for local, email only accounts?
Azure AD B2C does not "auto-populate" any fields.
When you setup your sign-up policy or unified sign-up/sign-in policy you get to pick the Sign-up attributes. These are the attributes that are show to the user for him/her to provide and are then stored in Azure AD B2C.
Anything that the user is not prompted for is left empty or in a few select cases (like name as you have observed) set to 'unknown'.
Azure AD B2C can not make assumptions as to what to pre-populate a given attribute with. While you might find it acceptable to use the email as the default for the name, others might not. Another example, the display name, for some, can be prepopulated with "{Given name} {Surname}", but for others, it's the other way around "{Surname, Givenname}".
What you are effectively asking for is an easy way to configure defaults for some attributes which is not that's available today. You can request this feature in the Azure AD B2C UserVoice forum.
At this time, you have two options:
Force your users to explicitly provide this value by select it as a sign-up attribute in your policy.
Add some code that updates these attributes with whatever logic you want (for example in the controller that processes new sign-ups or via a headless client running periodically).
Here's a quick & dirty snippet of .Net code that you can use for this (assuming you want to do this in the auth pipeline (Startup.Auth.cs):
private async Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
try
{
var userObjectId = notification.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
// You'll need to register a separate app for this.
// This app will need APPLICATION (not Delegated) Directory.Read permissions
// Check out this link for more info:
// https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(string.Format(graphAuthority, tenant));
var t = await authContext.AcquireTokenAsync(graphResource, new ClientCredential(graphClientId, graphClientSecret));
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + t.AccessToken);
var url = graphResource + tenant + "/users/" + userObjectId + "/?api-version=1.6";
var name = "myDisplayName";
var content = new StringContent("{ \"displayName\":\"" + name + "\" }", Encoding.UTF8, "application/json");
var result = await client.PostAsync(url, content);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
You'll reference this method when you setup your OpenIdConnectAuthenticationOptions like so:
new OpenIdConnectAuthenticationOptions
{
// (...)
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenValidated = OnSecurityTokenValidated,
},
// (...)
};
I wrote this extension:
public static class ClaimsPrincipal
{
public static string Username(this System.Security.Claims.ClaimsPrincipal user)=> user.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
}
Now you can use
User.Identity.Name for name if you have this in your OpenId config in the Startup.cs
options.TokenValidationParameters = new TokenValidationParameters() { NameClaimType = "name" };
and User.Username if you include the extension

Azure App Services (Mobile Apps) AAD authentication token refresh

I am trying to use Azure Active Directory to perform login functions on my uwp app. This happens successfully however I cannot get it to refresh the token when it expires and always receive the error "Refresh failed with a 403 Forbidden error. The refresh token was revoked or expired." and so I have to bring up the login window again. I am using the version 2.1.0 and the following code to authenticate:
private async Task<bool> AuthenticateAsync(bool forceRelogon = false)
{
//string message;
bool success = false;
// Use the PasswordVault to securely store and access credentials.
PasswordVault vault = new PasswordVault();
PasswordCredential credential = null;
//Set the Auth provider
MobileServiceAuthenticationProvider provider = MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory;
MobileServiceUser user = null;
try
{
// Try to get an existing credential from the vault.
var credentials = vault.FindAllByResource(provider.ToString());
credential = credentials.FirstOrDefault();
}
catch (Exception ex)
{
// When there is no matching resource an error occurs, which we ignore.
Debug.WriteLine(ex);
}
if (credential != null && !forceRelogon)
{
// Create a user from the stored credentials.
user = new MobileServiceUser(credential.UserName);
credential.RetrievePassword();
user.MobileServiceAuthenticationToken = credential.Password;
// Set the user from the stored credentials.
App.MobileService.CurrentUser = user;
//message = string.Format($"Cached credentials for user - {user.UserId}");
// Consider adding a check to determine if the token is
// expired, as shown in this post: http://aka.ms/jww5vp.
if (RedemptionApp.ExtensionMethods.TokenExtension.IsTokenExpired(App.MobileService))
{
try
{
await App.MobileService.RefreshUserAsync();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
success = true;
}
else
{
try
{
// Login with the identity provider.
user = await App.MobileService
.LoginAsync(provider);
// Create and store the user credentials.
if (credential != null)
vault.Remove(credential);
credential = new PasswordCredential(provider.ToString(),
user.UserId, user.MobileServiceAuthenticationToken);
vault.Add(credential);
success = true;
//message = string.Format($"You are now logged in - {user.UserId}");
}
catch (MobileServiceInvalidOperationException)
{
//message = "You must log in. Login Required";
}
}
//var dialog = new MessageDialog(message);
//dialog.Commands.Add(new UICommand("OK"));
//await dialog.ShowAsync();
return success;
}
Can anyone see something wrong with what I am doing, or need to do anything within the AAD service provider?
You might be able to get more accurate information by taking a look at the server-side application logs. Token refresh failure details will be logged there automatically. More details on application logs can be found here: https://azure.microsoft.com/en-us/documentation/articles/web-sites-enable-diagnostic-log/. I recommend setting the trace level to Informational or Verbose.
Also, if you haven't done this already, Azure AD requires a bit of extra configuration to enable refresh tokens. Specifically, you need to configure a "client secret" and enable the OpenID Connect hybrid flow. More details can be found in this blog post: https://cgillum.tech/2016/03/07/app-service-token-store/ (scroll down to the Refreshing Tokens section and see where it describes the process for AAD).
Besides what has been said about mobile app configuration, I can spot this.
You have:
// Login with the identity provider.
user = await App.MobileService.LoginAsync(provider);
It should be:
user = await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory,
new Dictionary<string, string>() {{ "response_type", "code id_token" }});
Maybe this will help:
https://azure.microsoft.com/en-us/blog/mobile-apps-easy-authentication-refresh-token-support/

Authorization failure when creating a Stream Analytics job

I've been trying (and failing) to create an Azure Stream Analytics job programatically. I was following this example originally:
https://azure.microsoft.com/en-gb/documentation/articles/stream-analytics-dotnet-management-sdk/
But it pops up a dialog for you to log in. I want to be able to do this server side. It looks like I need to use Azure AD to use the Resource Manager APIs. I've been working my way through this:
https://msdn.microsoft.com/en-us/library/azure/dn790557.aspx#bk_portal
And the code looks like this:
var authContext = new AuthenticationContext("https://login.microsoftonline.com/{tenant id}/oauth2/token");
var clientId = "{app client id}";
var appKey = "{app key}";
var subscriptionId = "{subscription id}";
var clientCredential = new ClientCredential(clientId, appKey);
var result = authContext.AcquireToken("https://management.core.windows.net/", clientCredential);
var creds = new TokenCloudCredentials(subscriptionId, result.AccessToken);
var client = new StreamAnalyticsManagementClient(creds);
var jobCreateParameters = new JobCreateOrUpdateParameters
{
Job = new Job
{
Name = streamAnalyticsJobName,
Location = "North Europe",
Properties = new JobProperties
{
EventsOutOfOrderPolicy = EventsOutOfOrderPolicy.Adjust,
Sku = new Sku
{
Name = "Standard"
}
}
}
};
var jobCreateResponse = client.StreamingJobs.CreateOrUpdate(resourceGroupName, jobCreateParameters);
I can successfully acquire a token, but creating the job fails:
AuthorizationFailed: The client 'REDACTED' with object id 'REDACTED' does not have authorization to perform action 'Microsoft.StreamAnalytics/streamingjobs/write' over scope '/subscriptions/REDACTED/resourcegroups/REDACTED/providers/Microsoft.StreamAnalytics/streamingjobs/REDACTED'
Am I doing something wrong? The app has the delegated permissions set.
UPDATE - 08-Dec-2015
There's an easy way to assign roles to Service Principals now. Please see this link for more details: https://azure.microsoft.com/en-in/documentation/articles/resource-group-create-service-principal-portal/.
Original Response
When you grant access to an application to your Azure subscription, behind the scenes a user is created with Service Principal user type in your Azure AD. The code you're using below assumes that you're using this Service Principal user when getting access token.
var clientCredential = new ClientCredential(clientId, appKey);
var result = authContext.AcquireToken("https://management.core.windows.net/", clientCredential);
var creds = new TokenCloudCredentials(subscriptionId, result.AccessToken);
However by default this user is not granted any permissions (RBAC) on your subscription and that's why you're getting the authorization error.
To solve this problem, what you would need to do is grant appropriate role to this user in your subscription. Now you can use PowerShell to do so or you can do it via code using ADAL library + making some web requests.
What I did was I made use of ADAL library to get access tokens and then used Google Postman (or Fiddler) to do other stuff. In my case, it was a web application. Here's what I did:
I logged in into the application as Global Administrator (Subscription Owner) and got a code. Using that code and ADAL library, I got the access token (let's call it token1).
var authContext = new AuthenticationContext(string.Format("{0}/common", signinEndpoint));//signinEndpoint = https://login.windows.net
var result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, redirectUri, credential);
I copied the tenant id and access token returned to me in result above.
Next thing I did was I found out the object id of the Service Principal user using POSTMAN. This is the GET URL I executed there. For Authorization header, you would need to use Bearer {token1}.
https://graph.windows.net/{subscription-id}/servicePrincipals?api-version=1.5&$filter=appId eq '{app-client-id}'
After that I acquired another access token (let's call it token2) for Service Management API operation using the code below:
authContext = new AuthenticationContext(string.Format("{0}/{1}", signinEndpoint, result.TenantId));
result = await authContext.AcquireTokenSilentAsync(serviceManagementApiEndpoint, credential, new UserIdentifier(request.UserInfo.UniqueId, UserIdentifierType.UniqueId));//serviceManagementApiEndpoint = https://management.core.windows.net/
After that I listed the roles in my subscription and picked the role I wanted to assign to my Service Principal user. In my case, I wanted to assign a Reader role so I noted down the role's id. For Authorization header, you would need to use Bearer {token2}.
https://management.azure.com/subscriptions/{subscription-id}/providers/Microsoft.Authorization/roleDefinitions?api-version=2015-06-01
Next is assignment of this role to the user. For this I created a guid as role assignment id and used the following URL:
https://management.azure.com/subscriptions/{subscription-id}/providers/microsoft.authorization/roleassignments/{role-assignment-id}?api-version=2015-06-01
It's going to be a PUT request and this was the request body would be something like:
{
"properties":
{
"roleDefinitionId": "{role id of the selected role from step 5}",
"principalId": "{id of the service principal from step 3"
}
}
Please ensure that the content-type of the request is set as application/json;odata=verbose and not application/json.
That's pretty much it! After that your code should work just fine :)
Give it a try and see what happens.

Resources