Google classroom authentication based on Domains - google-classroom

Presently i am working on google classroom API to integrate classroom into my .NET product.My problem is when i execute this method it asking authentication for first time but when i execute this code next time it directly log in as previous log in credentials.Even i change the client_secret.json of another(2nd) domain also it directly login as 1st domain authenticated user.My requirement is when i change the client_secret.json file dynamically in code at run time it will directly log in as domain user of this client_secret.json file rather than previous domain user of client_secret.json file.Is this possible?
If yes How can i achieve this.Please any one help on this.
private ClassroomService getservice()
{
using (var stream =
new FileStream(Server.MapPath("client_secret.json"), FileMode.Open, FileAccess.Read))
{
string credPath = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
}
var service = new ClassroomService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
return service;k
}

The client_secret.json file holds application-specific credentials, not user- or domain-specific credentials. Changing the client_secret.json file is not the correct way to log in/out users. Instead, you'll need to use a FileDataStore with a different path.

Related

Getting an error while retrieving a blob using user assigned managed identity

We have a C# code which used to retrieve a blob from storage account. The authentication is done using user assigned service principal. These things works till December. But now we are getting some weird error as follows.
ManagedIdentityCredential authentication unavailable. The requested identity has not been assigned to this resource.
Status: 400 (Bad Request)
Content:
{"error":"invalid_request","error_description":"Identity not found"}
The managed identity has storage data blob contributor access in the storage account.
Attaching the code for reference:
public static async Task<string> GetBlobAsync()
{
string storageName = "storage account name";
Uri blobUri = new Uri("blob uri");
TokenCredential cred = new ManagedIdentityCredential("client id");
var blobClient = new BlobClient(blobUri, cred, null);
try
{
var downloadInfo = await blobClient.DownloadAsync();
using (TextReader reader = new StreamReader(downloadInfo.Value.Content))
{
string metadataBlob = await reader.ReadToEndAsync();
return metadataBlob;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine("");
return null;
}
P:S: the three environmental variables such as app id, app secret and tenant id are correct.
I have been stuck here for almost a month. Nothing works.
This document demonstrates how to use managed identity to access App Configuration from App Service, but you can replace the App Service with any other Azure services that support managed identity. https://learn.microsoft.com/en-us/azure/azure-app-configuration/howto-integrate-azure-managed-service-identity
Here are a few things I'd like to call out
Make sure the managed identity is enabled in the Azure service where your application runs.
When you are using system assigned managed identity, you don't need to provide the client Id. You only need to provide the client Id when you use user assigned managed identity.
Make sure the managed identity is granted either App Configuration Data Reader or App Configuration Data Owner role in the access control of your App Configuration instance.
Wait for at least 15 minutes after the role assignment for the permission to propagate.
Managed identity can ONLY work when your code is running in the Azure service. It will NOT work when running locally
Try this
Uri blobUri = new Uri("blob uri");
var cred = new DefaultAzureCredential(
new DefaultAzureCredentialOptions {
ManagedIdentityClientId = "your client id" });
var blobClient = new BlobClient(blobUri, cred, null);
ref: https://learn.microsoft.com/pt-br/dotnet/api/azure.identity.defaultazurecredential?view=azure-dotnet
Option 2 (work for me)
Create Identity and add in app service
Assign RBAC "Storage Blob Data Contributor" to your storage resource.
Add Key AZURE_CLIENT_ID (clientid of the identity that was created) in Environment App Service
Code to access blob
(you don't need to specify client id in the code because it will use the AZURE_CLIENT_ID configured in the AppService)
app.MapGet("/read", async () =>
{
Uri blobUri = new Uri("https://xxxx.blob.core.windows.net/texts/text.txt");
var cred = new DefaultAzureCredential();
var blobClient = new BlobClient(blobUri, cred, null);
var downloadInfo = await blobClient.DownloadAsync();
using (TextReader reader = new StreamReader(downloadInfo.Value.Content))
{
string metadataBlob = await reader.ReadToEndAsync();
return metadataBlob;
}
});
Result print

403 Current user cannot create courses [forbidden] while creating a course for Google classroom api (.Net)

I have created a VisualStudio (C#) project, wihch use google classroom apis.
With Super admin, It can create a course, but with delegated admin, It returns an error.
I have use a delegated admin. Why does that error return ?
Errors [
Message[#UserCannotCreateCourse Current user cannot create courses.] Location[ - ] Reason[forbidden] Domain[global]
]
my code is below given
string[] Scopes = {ClassroomService.Scope.ClassroomCourses};
UserCredential credential;
using (var stream = new FileStream("credentials.json", FileMode.Open, FileAccess.Read))
{
string credPath = "token.json";
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
}
var service = new ClassroomService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Classroom test"
});
var body = new Course()
{
Name = "MyFirstClass",
OwnerId = "me"
};
var course = service.Courses.Create(body).Execute();
"credentials.json"File is from My Google Cloud Platform project.
My project is verificated.
./auth/classroom/courses
./auth/classroom/rosters
(etc.)
Delegated admin is created by Google Console.
I added custom admin role.
I found that the owner of the classroom course must a member of classroom_teachers Group. (If the account is not the super admin)
With an account that has not be a teacher, classroom api gets error.
And after becoming a teacher (manually sign-in, and create a classroom course), classroom api succeeded.
So, before creating a course, I join that account into classroom_teacher group.
Thank you.

Login Required 401 using Google ServiceAccountCredential using Google Admin Directory API

I have tried to follow the simple example listed here: https://developers.google.com/admin-sdk/directory/v1/quickstart/dotnet
The difference is I generated a Service Account Credential, and assigned it as a Delegate with the Role Project Owner, so it has full access. I also assigned it the proper namespaces for scopes.
Here it has access to orgunits which is what I'm trying to list in the Directory API
Here is my service account defined
Here are my credentials
I downloaded the JSON for the credential and added it to my project. I can confirm that the code loades the ServiceAccountCredential and successfully authenticates and gets an access token by inspecting the debugger.
But then I pass the credential to the Service Initializer, and when I create and execute a request it fails with
{"Google.Apis.Requests.RequestError\r\nLogin Required [401]\r\nErrors [\r\n\tMessage[Login Required] Location[Authorization - header] Reason[required] Domain[global]\r\n]\r\n"}
Here's the code:
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using System;
using System.Collections.Generic;
using System.IO;
namespace DirectoryQuickstart
{
class Program
{
static string[] Scopes = { DirectoryService.Scope.AdminDirectoryUser, DirectoryService.Scope.AdminDirectoryOrgunit };
static string ApplicationName = "slea-crm";
static string Secret = "gsuite-secret.json";
static void Main(string[] args)
{
ServiceAccountCredential sac = GoogleCredential.FromFile(Secret).CreateScoped(Scopes).UnderlyingCredential as ServiceAccountCredential;
var token = sac.GetAccessTokenForRequestAsync().Result;
// Create Directory API service.
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = sac,
ApplicationName = ApplicationName,
});
OrgunitsResource.ListRequest request = service.Orgunits.List(customerId: "REDACTED");
IList<OrgUnit> orgUnits = request.Execute().OrganizationUnits;
if (orgUnits != null && orgUnits.Count > 0)
{
foreach (var orgUnit in orgUnits)
{
Console.WriteLine("{0} ({1})", orgUnit.Name, orgUnit.OrgUnitPath);
}
}
else
{
Console.WriteLine("No orgunits found.");
}
Console.Read();
}
}
}
Here is the content of my JSON secret (with redactions)
What am I missing here?
EDIT: OK, I breakpoint the code while it generates the request, and I can see that no where does it set the Authorization token bearer in the headers. Why? I would expect this HttpClientInitializer class to take care of that, since the API docs say it knows how to handle that, and every example on the internet I've found shows it just passing the credential into the service initializer. But when I walked through it, even though the credential has already been granted an access token and one exists within it, nowhere does the request have the header updated.
The only thing I can see is there is some way to add an HTTP request interceptor where possibly I could do this myself, but wow, this seems really...bizarre -- after all this work they did on the dotnet client SDK, I honestly could have just written direct to the HTTP API and it would have been a lot simpler and easier to follow.
The missing piece of the puzzle is this line:
ServiceAccountCredential sac = GoogleCredential.FromFile(Secret)
.CreateScoped(Scopes)
.UnderlyingCredential as ServiceAccountCredential;
Needs to be modified to this:
static string userName = "admin#yourdomain.com" // valid user in your org
ServiceAccountCredential sac = GoogleCredential.FromFile(Secret)
.CreateScoped(Scopes)
.CreateWithUser(userName)
.UnderlyingCredential as ServiceAccountCredential;
Java/Python/Go sample of doing similar is here: https://developers.google.com/admin-sdk/directory/v1/guides/delegation#create_the_service_account_and_its_credentials
This has been answered but adding more details here. If anyone wants to impersonate user to upload file on google drive using Service account. Follow these steps
Create Service Account
Enable Site Wide delegation for service account
Get Service account client ID
Enable Client ID to use Google Drive API using Google Admin Console->Manage API
Use the below C# code to upload file
public static DriveService GetService()
{
string[] scopes = new string[] { DriveService.Scope.Drive };
//"SERVICE_ACCOUNT_EMAIL_HERE";
String serviceAccountEmail = "test-417#elated-graph-261115.iam.gserviceaccount.com";
// Scope and user email id which you want to impersonate
var initializer = new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes,
User = "yourEmail#domain.com"
};
//get private key, from .JSON file
var credential = new ServiceAccountCredential(initializer.FromPrivateKey("-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkHeAicu6uFQn0\n7KUVTjgZ68nQui8+c8NmKW8aW8vhkBIKfdewXFECiUlTMPyI+HXbubsCK5Dl2xBS\nnphLq6YyE0xEQxNFLYHwfUKuzGQ2rV+qObcZ0mLZjCaf+pw3YiRVuU6OtslLJKJH\n-----END PRIVATE KEY-----\n"));
// Create the service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "DriveAPI",
});
service.HttpClient.Timeout = TimeSpan.FromMinutes(100);
return service;
}
That's it, we are done above Code is using Impersonation/Delegation for uploading file on Google Drive using Service account
Reference : Upload file to Google Drive using Service Account in C# MVC (With Impersonation)

Azure AD userAssertion: Token missing scope "Directory.Read.All"

I have a Web API and a UI application that used ADAL library to call the Web API.
I already gave DELEGATED PERMISSIONS (Read directory data) for both Web API and UI application while registering the apps to Azure AD.
I have below code in Web API to save Token for log-In user,
private void ConfigureAuthentication(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters { SaveSigninToken = true, ValidAudience = ConfigurationManager.AppSettings["ida:Audience"] }
});
}
Now in Web API controllers, I am trying to get token to access Microsoft AD Graph API using below code,
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
string userName = "test#onmicrosoft.com";
string userAccessToken = bootstrapContext.Token;
UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
var authContext = new AuthenticationContext(string.Format(CultureInfo.InvariantCulture, aadInstance, tenant));
var clientCred = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync("https://graph.windows.net", clientCred, userAssertion);
accessToken = result.AccessToken;
The above code giving me token back but the scope values is below,
`scp: "User.Read"`
Question - Why the token not giving directory access (Directory.Read.All) as I already set Directory access?
`scp: "Directory.Read.All User.Read"`
Update:
I am missing Grant Permission for Read Directory Data under DELEGATED PERMISSIONS. After giving Grant Permission I am able to get token with scope scp: "Directory.Read.All User.Read"
If I understand correctly, you want to use Microsoft Graph API ,not Azure AD Graph API.
However, based on that the screenshot you post in this question is a v1 enpoint AAD Application, it does nothing about Microsoft Graph API which you're trying to approaching. So, whatever you changed on this application, the result shouldn be same. I suggest you register v2 enpoint Application in https://apps.dev.microsoft.com/
Here is a document which shows how to get auth tokens for using Microsoft Graph.
Hope this helps!

Different result

I'm trying to pull google contacts information (photos, gender and more) and we used Google People API to do that.
I use in createContact service to search contact by an Email.
I created a client id and client secret, and with the simple code of oAuth2.0, I got a refresh token that my server used to generate credentials.
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
String clientId = "";
String clientSecret = "";
String scope = "https://www.googleapis.com/auth/contacts";
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, jsonFactory, clientId, clientSecret,
Arrays.asList(scope))
.setAccessType("offline")
.setApprovalPrompt("force")
.build();
LocalServerReceiver localReceiver = new LocalServerReceiver.Builder().setPort(8089).build();
Credential credential = new AuthorizationCodeInstalledApp(flow, localReceiver).authorize("user");
In that way, I get the refreshToken and save it offline in a secured file.
After that, my application connect to google and get the clientId, ClientToken, and RefreshToken from that file and try to connect -
GoogleCredential googleCredential = new GoogleCredential.Builder()
.setJsonFactory(jsonFactory)
.setTransport(transport)
.setClientSecrets(googlePeopleKey.getClientId(), googlePeopleKey.getClientSecret())
.build().setRefreshToken(googlePeopleKey.getRefreshToken());
It seems that the result is different per user. When I operate with a refresh code that my user authenticate earlier, I can't get all the information that another user in my company (that authorized by the same way) does. Today, it flipped, and in another test, I get results that he didn't. Very strange.
I was looking up about limit rate but, it seems that I even not near the limitations.
Do you have any idea why it returns different result for different users? The different result means that sometimes one of the users can see the profile picture but the other one can't.
Thank you!

Resources