Get group members with an admin account / deprecated GroupsManager class - google-apps-script

The class GroupsManager will be turned off by November 20, 2014 https://developers.google.com/apps-script/reference/domain/groups-manager
I am using this class to manage some groups with my admin account as it's allowing access to group members WITHOUT BEING MEMBER OF.
Sample code here:
var group = GroupsManager.getGroup("some-group-name#mydomain.com");
var members = group.getAllMembers();
Using the Groups Service https://developers.google.com/apps-script/reference/groups/ is not an option, as stated in the documentation: "This service allows scripts to access Google Groups. It can be used to query information such as a group's email address, or the list of groups in WHICH THE USER IS MEMBER OF."
Using the AdminSDK as proposed in the GroupsManager "To manage your domain, use the Admin SDK Directory and Admin SDK Reports advanced services instead." is neither an option as there is no method to retrieve the members of a group...
So, anyone has a clue?
Thank you,
Franck

You can use the Admin SDK Directory service to do this. The method AdminDirectory.Members.list lists all the members of a group.

Directory service = null;
try
{
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(Arrays.asList(DirectoryScopes.ADMIN_DIRECTORY_USER))
.setServiceAccountUser(userEmail)
.setServiceAccountPrivateKeyFromP12File(
new java.io.File(SERVICE_ACCOUNT_PKCS12_FILE_PATH))
.build();
service = new Directory.Builder(httpTransport, jsonFactory, null)
.setHttpRequestInitializer(credential).setApplicationName("ShareFileUSer").build();
}catch(Exception e)
{
System.out.println("Exception is : "+e);
}
Directory.Users.List userList = service.users().list().setCustomer("my_customer").setMaxResults(100);

Related

Why "File not found" when using fileId of shared drive in google-api-services-drive?

The code below is trying to grant a group fileOrganizer permission for a shared drive which I created earlier using similar code and the same service account impersonating the same user. (I've redacted the actual DRIVE_ID with a fake one)
public class DriveQuickstart {
public static void main(String... args) throws IOException, GeneralSecurityException {
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
final List<String> SCOPES = Collections.singletonList(DriveScopes.DRIVE);
final String JSON_PATH = "my.json";
final String USER = "user#domain.com";
final String GROUP = "group#domain.com";
final String DRIVE_ID = "bbb";
GoogleCredential credential = GoogleCredential
.fromStream(new FileInputStream(JSON_PATH))
.createScoped(SCOPES)
.createDelegated(USER);
Drive service = new Drive.Builder(HTTP_TRANSPORT,
JSON_FACTORY,
credential).build();
DriveList result = service.drives().list().execute();
System.out.println("drive list " + result);
System.out.println("about to grant permission on id " + DRIVE_ID);
service
.permissions()
.create(DRIVE_ID,
new Permission()
.setType("group")
.setRole("fileOrganizer")
.setEmailAddress(GROUP))
.execute();
}
}
As you can see from the output below, my code is able to list my two existing drives. The second one (with the id redacted to bbb) is the one I want to share with the group. When I use that same id in creating a Permission, the API responds with File not found:
drive list {"drives":[{"id":"aaa","kind":"drive#drive","name":"Manually Created Shared Drive"},
{"id":"bbb","kind":"drive#drive","name":"Shared Drive Created By Service Account"}],
"kind":"drive#driveList"}
about to grant permission on id bbb
Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 404 Not Found
{
"code" : 404,
"errors" : [ {
"domain" : "global",
"location" : "fileId",
"locationType" : "parameter",
"message" : "File not found: bbb.",
"reason" : "notFound"
} ],
"message" : "File not found: bbb."
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:150)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:40)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:444)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1108)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:542)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:475)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:592)
at DriveQuickstart.main(DriveQuickstart.java:63)
Why does it say File not found?
Do I need to setSupportsAllDrives(true) in order to include shared drives as opposed to being limited to My Drive? If so where?
The documentation says after June 1, 2020 all applications are assumed to support shared drives. I'm using the latest possible dependencies as of today (June 11), but all of these pre-date June 1. Here's the dependencies section from my build.gradle file:
dependencies {
compile 'com.google.api-client:google-api-client:1.30.9'
compile 'com.google.auth:google-auth-library-oauth2-http:0.20.0'
compile 'com.google.apis:google-api-services-drive:v3-rev20200413-1.30.9'
}
Figured it out:
service
.permissions()
.create(DRIVE_ID,
new Permission()
.setType("group")
.setRole("fileOrganizer")
.setEmailAddress(GROUP))
.setSupportsAllDrives(true) // <---- add this here
.execute();
As for the plan to assume all apps support shared drives, got this via email from Google Support:
There was a plan to activate this by default as of June 1st, but this
is being reconsidered, and it will be necessary to add this parameter
to access shared drives for the foreseeable future. Our documentation
should be in the process of being updated.

Amazon Pay Integration : Where can I get preSharedEncodedKey?

I'm trying to integrate Amazon Pay for receiving merchant payments and I see a method which receives preSharedEncodedKey. But I can't find that key anywhere in document. Where can I find it?
It is mentioned in [22 January 2019 Amazon Pay Integration Guide 26]
private static byte[] encryptMerchantKey(final byte[] key) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException
{
KeyFactory keyFact = KeyFactory.getInstance(RSA);
KeySpec spec = new X509EncodedKeySpec(org.bouncycastle.util.encoders.Base64.decode("preSharedEncodedKey"));
PublicKey publicKey = keyFact.generatePublic(spec);
Cipher cipher = RSA_THREAD_CIPHER.get();
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(key);
}
Topic on forum: https://forums.aws.amazon.com/thread.jspa?threadID=104446
As I figured out that the value will be available at AWS merchant account. I can't post the location of it/screenshot as I don't hold the AWS merchant account. But my client gave the "preSharedEncodedKey" which is a long uuid.

Google Drive API - Granting Access to Files in a Folder to Service Account

I am using a service account to connect to the Google Drive of a G Suite. I understand that with the available access (scope: https://www.googleapis.com/auth/drive.file) I can only see the files that the service account has created and using the /auth/drive scope (which gives full visibility) requires special Google approval.
I need to be able to expand the service account's visibility to include files in at least 1 folder the user has not created. I can't get this through normal folder sharing as best I can tell.
Does anyone know how to do this?
Edit to include code (written in Apex, which is similar to Java). Still pretty rough, I haven't cleaned it up yet but:
private static String buildAuthBody(Google_Drive_Integration__mdt mdt) {
//Builds and encodes the JWT header
String bodyHeader = '{"alg":"RS256","typ":"JWT"}';
String encodedHeader = encode(bodyHeader);
//Builds and encodes the JWT Claim Set. See googleAuth body
googleAuth ga = new googleAuth(mdt);
String claimSetString = JSON.serialize(ga);
String encodedClaimSet = encode(claimSetString);
//Builds out necessary pieces for Crypt.sign(algorithmName, input, privateKey). Input = body
String signatureBody = encodedHeader + '.' + encodedClaimSet;
signatureBody = signatureBody.replaceAll('=','');
String encodedSignatureBody = EncodingUtil.urlEncode(signatureBody,'UTF-8');
Blob signatureBodyBlob = Blob.valueOf(encodedSignatureBody);
Blob key = EncodingUtil.base64Decode(mdt.Service_Account_Private_Key__c); //Must be decoded to pass into method w/o error
//Builds the signature
Blob signatureBlob = Crypto.sign('RSA-SHA256', signatureBodyBlob , key);
String encodedSignature = encodeBlob(signatureBlob);
//Sets grant type
String grantType = EncodingUtil.urlEncode('urn:ietf:params:oauth:grant-type:jwt-bearer', 'UTF-8');
//Sets body and debugs to rebuild body
System.debug('grant type: grant_type=' + grantType);
System.debug('&assertion=');
System.debug('encoded header: '+encodedHeader);
System.debug('encoded claim set: '+encodedClaimSet);
System.debug('encoded signature: '+encodedSignature);
//Build and return the body
String body = 'grant_type=' + grantType;
body += '&assertion=';
body += signatureBody;
body += '.' + encodedSignature;
return body;
}
class googleAuth {
public String iss; //'test-google-drive#sapient-flare-252622.iam.gserviceaccount.com';
public String scope = 'https://www.googleapis.com/auth/drive';
public String aud = 'https://www.googleapis.com/oauth2/v4/token';
public Long exp;
public Long iat;
googleAuth(Google_Drive_Integration__mdt mdt) {
DateTime dt = DateTime.now();
iat = dt.getTime() / 1000;
exp = iat + 3600;
iss = mdt.Service_Account_User_Email__c;
}
}
private static String encode(String str) {
Blob b = Blob.valueOf(str);
String ret = EncodingUtil.base64Encode(b);
ret = EncodingUtil.urlEncode(ret, 'UTF-8');
return ret;
}
private static String encodeBlob(Blob b) {
String ret = EncodingUtil.base64Encode(b);
ret = EncodingUtil.urlEncode(ret, 'UTF-8');
return ret;
}
I think you are misunderstanding things about service accounts and about scopes.
Scopes define the amount of access a user has granted to your application. In this case the user is the service account. Using scope: https://www.googleapis.com/auth/drive.file with a service account doesn't make much sense really as you the developer own the service account and there by own its drive account. so really just give it full access using scope: https://www.googleapis.com/auth/drive there is no real reason for you to limit it. If you had a normal application using Oauth2 you would only request the access you need to the users drive account.
I need to be able to expand the service account's visibility to include files in at least 1 folder the user has not created. I can't get this through normal folder sharing as best I can tell.
I am not sure i understand this. Assuming that when you say "user" you mean the service account. I am going to assume this folder is part of your gsuite system. In which case you simply need to have your Gsuite admin set up domain wide delegation to the service account and it will have access. This is a way of granting the service account permissions to access data on your gsuite account kind of like adding a new user to the system.

What's the best way to migrate to ServiceStack authentication framework when stuck with my_aspnet_* tables

I'm not quite ready to change up all my user/auth tables from the MySQL user/roles/profile provider format, but am moving off of MVC to ServiceStack.
Is there a pre-built IUserAuthRespository and/or CredentialsAuthProvider somewhere that can be used, or do I need to build one to provide this mapping?
If I need to build one, I assume implementing at the IUserAuthRepository level is the cleanest? Is there a minimum set of methods required to implement basic login/logout (and administrative "switch user" impersonation) functionality?
I tried implementing a custom CredentialsAuthProvider, which seems to work, but I'm unable to get local posts for impersonation to use the proper provider. Looking for a solution to that, I realized that maybe its better to implement the repository instead.
EDIT:
My current registration of the custom auth provider is:
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[]
{
container.Resolve<MySqlCredentialsAuthProvider>() //HTML Form post of UserName/Password credentials
}));
And calling code for the local post to the AuthenticateService is:
[RequiredRole(SystemRoles.Administrator)]
public object Any(ImpersonateUser request)
{
using (var service = base.ResolveService<AuthenticateService>()) //In Process
{
//lets us login without a password if we call it internally
var result = service.Post(new Authenticate
{
provider = AuthenticateService.CredentialsProvider,
UserName = request.Username,
//Password = "should-not-matter-since-we-are-posting-locally"
});
return result;
}
}
Integrating with existing User Auth tables
If you want to use your existing User/Auth tables, the easiest solution is to ignore the UserAuth repositories and implement a Custom CredentialsAuthProvider that looks at your existing database tables to return whether their Authentication attempt was successful.
Implement OnAuthenticated() to populate the rest of your typed IAuthSession from your database, e.g:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
//Add here your custom auth logic (database calls etc)
//Return true if credentials are valid, otherwise false
}
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
//Fill IAuthSession with data you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
//...
//Call base method to Save Session and fire Auth/Session callbacks:
return base.OnAuthenticated(authService, session, tokens, authInfo);
//Alternatively avoid built-in behavior and explicitly save session with
//authService.SaveSession(session, SessionExpiry);
//return null;
}
}
Importing existing User Auth tables
If you want to import them into an OrmLite User Auth tables, you would configure to use the OrmLiteAuthRepository in your AppHost:
//Register to use MySql Dialect Provider
container.Register<IDbConnectionFactory>(
new OrmLiteConnectionFactory(dbConnString, MySqlDialect.Provider));
Plugins.Add(new AuthFeature(
() => new CustomUserSession(), //Use your own typed Custom UserSession type
new IAuthProvider[] {
//HTML Form post of UserName/Password credentials
new CredentialsAuthProvider()
}));
//Tell ServiceStack you want to persist User Info in the registered MySql DB above
container.Register<IUserAuthRepository>(c =>
new OrmLiteAuthRepository(c.Resolve<IDbConnectionFactory>()));
//Resolve instance of configured IUserAuthRepository
var userAuth = container.Resolve<IUserAuthRepository>();
//Create any missing UserAuth RDBMS tables
authRepo.InitSchema();
Then to import your data you can use the above MySQL DB connection to select from your existing tables then use the IUserAuthRepository to create new Users.
// Open DB Connection to RDBMS
using (var db = container.Resolve<IDbConnectionFactory>().Open())
{
//Example of fetching old Users out of a custom table (use your table instead)
var oldUsers = db.Select<OldUserInfo>();
// Clear existing UserAuth tables if you want to replay this import
//db.DeleteAll<UserAuthDetails>();
//db.DeleteAll<UserAuth>();
//Go through and create new User Accounts using Old User Info
foreach (var oldUser in oldUsers)
{
//Create New User Info from Old Info
var newUser = new UserAuth {
UserName = oldUser.UserName,
Email = oldUser.Email,
//...
};
//Create New User Account with oldUser Password
authRepo.CreateUserAuth(newUser, oldUser.Password);
}
}
After this you'll have new User Accounts from your old User Info which you can sign in with.

Fetching access token from refresh token using Java

I got the refresh token and access token from the authorization code by using the sample program given here https://developers.google.com/drive/credentials#retrieve_oauth_20_credentials
But there is no sample program to get the access token from refresh token i.e., when we dont have authorization code. Any pointers? Is there any way to Instantiate a drive service object using only refresh token and access token?
The DrEdit Java sample has an example on how to retrieve stored Credentials from the Google App Engine Datastore.
If using another type of credentials store, you can use the following code to instantiate new OAuth 2.0 Credentials using stored tokens:
GoogleCredential credentials = new GoogleCredential.Builder()
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.setJsonFactory(jsonFactory).setTransport(transport).build()
.setRefreshToken("<REFRESH_TOKEN>").setAccessToken("<ACCESS_TOKEN>");
EDIT: Corrected a typo in the code.
Try this code. This is a mix solution from Alain + https://cloud.google.com/bigquery/authorization. this worked for me.
GoogleCredential credential = createCredentialWithRefreshToken(
HTTP_TRANSPORT, JSON_FACTORY, new TokenResponse().setRefreshToken(refreshToken));
credential.refreshToken();
String newAccessToken = credential.getAccessToken();
public static GoogleCredential createCredentialWithRefreshToken(HttpTransport transport,
JsonFactory jsonFactory, TokenResponse tokenResponse) {
return new GoogleCredential.Builder().setTransport(transport)
.setJsonFactory(jsonFactory)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.build()
.setFromTokenResponse(tokenResponse);
}
Just to explain my experience.
Had the same problem (access token to null) the problem was that I had been tested without sending setAccessType ("offline") and from the account access was allowed access.
Then I put on my code setAccessType ("offline") code but still refresh token to null.
My solution was to revoke permission from the account I want to access (https://accounts.google.com/b/0/IssuedAuthSubTokens?hl=en). In the next test I grant it.
Follow this example:
private static void getAccessTokenFromRefreshToken() throws IOException {
GoogleCredential credentials = new GoogleCredential.Builder()
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.setJsonFactory(JSON_FACTORY).setTransport(httpTransport).build()
.setRefreshToken(REFRESH_TOKEN);
String accessToken = credentials.getAccessToken();
System.out.println("Access token before: " + accessToken);
credentials.refreshToken();
accessToken = credentials.getAccessToken();
System.out.println("Access token after: " + accessToken);
}
Output:
Access token before: null
Access token after: ya29.u4HC22-Avc0aaDC0g0zj1jhz2yjsJrm8qm0hU2eVeBrf6DKj3CcHDQ42KARH4y_d364-b