Hangout Chat API authentication fails with Default Service account - java

I'm testing the new Google Chat bot that should post messages asynchronously, thus I must use GoogleCredentials to authenticate requests.
It works perfectly fine when I use GoogleCredentials.getApplicationDefault() while working locally, but when I deploy the bot to Cloud Run it stops.
idiomatic secure way (does not work)
The HangoutsChat is created the following way:
var credentials = GoogleCredentials.getApplicationDefault()
.createScoped(CHAT_BOT_SCOPE);
var credentialsAdapter = new HttpCredentialsAdapter(credentials);
var chat = new HangoutsChat.Builder(
GoogleNetHttpTransport.newTrustedTransport(),
JacksonFactory.getDefaultInstance(),
credentialsAdapter)
.setApplicationName(BOT_NAME)
.build();
So in order to work locally, I set GOOGLE_APPLICATION_CREDENTIALS and point it to the service account key like this: export GOOGLE_APPLICATION_CREDENTIALS=./credentials/adc.json. While in the cloud environment I expect the same service account to just work, but if not set explicitly from the file, the credentials throw insufficientPermissions error with PERMISSION_DENIED.
workaround (works fine)
As a workaround, I'm creating credentials from the service account stored in Secret Manager like this:
var serviceAccount = Secrets.chatServiceAccount();
var credentials = GoogleCredentials.fromStream(streamFrom(serviceAccount))
.createScoped(CHAT_BOT_SCOPE);
var credentialsAdapter = new HttpCredentialsAdapter(credentials);
var chat = new HangoutsChat.Builder(
GoogleNetHttpTransport.newTrustedTransport(),
JacksonFactory.getDefaultInstance(),
credentialsAdapter)
.setApplicationName(BOT_NAME)
.build();
return chat;
private static InputStream streamFrom(String data) {
return new ByteArrayInputStream(data.getBytes(Charset.defaultCharset()));
}
The question is: is anyone able to use default credentials for HangoutsChat API? Is it even possible or I am missing something here?

Related

How to store received WebAuthn private key in Selenium tests (java)

I am trying to build a selenium test (java) that goes through WebAuthn authentication. Thanks to thread here I am able to retrieve the private key. The question is how to store and reuse it? I tried to store private key to the disk
final PKCS8EncodedKeySpec privateKey = authenticator.getCredentials().get(0).getPrivateKey();
File outputFile = new File("./private.key");
Files.write(outputFile.toPath(), privateKey.getEncoded());
Then when I run test case for the same user I try to load it and create an instance of Credential like this:
Credential credential = Credential.createNonResidentCredential(
id, "null", new PKCS8EncodedKeySpec(key), /*signCount=*/1);
and load the credentials to my driver:
VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions();
options.setTransport(VirtualAuthenticatorOptions.Transport.INTERNAL)
.setHasResidentKey(true)
.setProtocol(VirtualAuthenticatorOptions.Protocol.CTAP2);
VirtualAuthenticator authenticator = ((HasVirtualAuthenticator) driver).addVirtualAuthenticator(options);
authenticator.addCredential(credential);
but the credential is got refused. Unfortunately I haven't found any information on how to transfer the credentials between the sessions. What is the usual workflow in this case? It should be quite common scenario I think
I have run into a similar issue, my use case is webauthn registration and login on different tabs ( = I have to transfer the credentials).
After registration I save the credentials of the user:
VirtualAuthenticator virtualAuthenticator = ((HasVirtualAuthenticator) Driver.get()).addVirtualAuthenticator(new VirtualAuthenticatorOptions());
// setup happens
Credential credential = authenticator.getCredentials().get(0);
userAccount.addCredential(credential);
On login I do this:
VirtualAuthenticator virtualAuthenticator = ((HasVirtualAuthenticator) Driver.get()).addVirtualAuthenticator(new VirtualAuthenticatorOptions());
Credential credential = userAccount.getFido2Credential();
Credential c = Credential.createNonResidentCredential (credential.getId(),"<whatever is your rpid(~domain)>", credential.getPrivateKey(), credential.getSignCount());
authenticator.addCredential(c);
My issue was that the rpid was null in the credential object and the authenticator's addCredential method failed because of this.

Receiving 401 when accessing authenticated Google Cloud Function

I am trying to invoke an authenticated HTTP-based cloud function from another cloud function. Let's call them CF1 and CF2 respectively, for the sake of brevity; thus I wish to invoke CF2 from CF1.
Following the example given by the Google Documentation: Authenticating for Invocation, I created a new service account for CF2, and then attached it to CF1 with the roles/cloudfunctions.admin . I downloaded a service key for local testing with Functions Framework, setting it as the Application Default Credentials(ADC); thus CF2 on my local machine connects to CF1 on GCP, authenticating as CF2's service account via ADC.
I have deployed CF1 on Cloud Functions successfully, and was testing whether CF2 on my local machine could reach to CF1 when I was surprised to receive a HTTP 401.
For reference, here is the code in question, which is almost identical to the samples provided by the Google Documentation:
String serviceUrl = "<cf1-url>";
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
if (!(credentials instanceof IdTokenProvider)) {
throw new IllegalArgumentException("Credentials are not an instance of IdTokenProvider.");
}
IdTokenCredentials tokenCredential =
IdTokenCredentials.newBuilder()
.setIdTokenProvider((IdTokenProvider) credentials)
.setTargetAudience(serviceUrl)
.build();
GenericUrl genericUrl = new GenericUrl(serviceUrl);
HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(tokenCredential);
HttpTransport transport = new NetHttpTransport();
com.google.api.client.http.HttpRequest request = transport.createRequestFactory(adapter).buildGetRequest(genericUrl);
com.google.api.client.http.HttpResponse response = request.execute();
I tried referring to:
Google Cloud Platform - cloud functions API - 401 Unauthorized
Cloud Function Permissions (403 when invoking from another cloud function)
Google Cloud Function Authorization Failing
but I was not able to find a solution to my problem from those questions.
Further testing revealed that the identity token generated via the client SDK:
tokenCredential.getIdToken().getTokenValue() is different from the GCloud CLI command gcloud auth print-identity-token. I could use the identity token generated by GCloud CLI to directly invoke CF1 (e.g. via Postman/cURL and authenticated as CF2's service account) but not the identity token printed by the client SDK. This was a surprise as I am using CF 2's service account keys as the ADC, and also authorized it for gcloud access via gcloud auth activate-service-account.
It seems to me that there is no issue with the permissions of the service accounts and cloud functions, as I can directly invoke CF1; thus it would appear to be an issue with the code. However, I am unable to determine the cause of the 401 error.
The target audience, your serviceURL, must be the raw url, this one provided by the Cloud Functions service.
If you add your parameters (query or path) it won't work.

SocketTimeOutException for PROXY call using GraphServiceClient, how to configure proxy

I am trying to get list of users from Azure B2C Active Directory, for un-proxy environment, my code is running fine, but when I am running it by passing proxy configuration, I am getting "SocketTimeoutException"
Below is my code...
GraphServiceClient<Request> graphClient;
if (this.proxy.equals("true")) {
final ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
.clientId(this.clientId)
.clientSecret(this.clientSecret)
.tenantId(this.b2cTenant)
.build();
final TokenCredentialAuthProvider tokenCredentialAuthProvider =
new TokenCredentialAuthProvider(Constant.scopes, clientSecretCredential);
final Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(this.hostAddress, this.hostPort));
final OkHttpClient httpClient = HttpClients.createDefault(tokenCredentialAuthProvider)
.newBuilder()
.proxy(proxy)
.build();
graphClient = GraphServiceClient.builder()
.authenticationProvider(tokenCredentialAuthProvider)
.httpClient(httpClient)
.buildClient();
} else {
final ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
.clientId(this.clientId)
.clientSecret(this.clientSecret)
.tenantId(this.b2cTenant)
.build();
final TokenCredentialAuthProvider tokenCredentialAuthProvider =
new TokenCredentialAuthProvider(Constant.scopes, clientSecretCredential);
graphClient = GraphServiceClient.builder()
.authenticationProvider(tokenCredentialAuthProvider)
.buildClient();
}
In "if" I am working with PROXY and in "else" I am working without PROXY.
So I have a hostAddress and hostPort which I am passing through command line...I am creating a ClientSecretCredential using clientId, clientSecret and b2cTenantId.
Then I am creating a TokenCredentialAuthProvider using scope and client secret credential.
For me scope is - https://graph.microsoft.com/.default
Then I am creating a Proxy using address and port, which I am passing to OkHttpClient. Then I am creating passing all of it to graphClient.
For non-proxy ("else") is working fine, but when I am working through proxy I am getting "Time out". I tried to debug code, but the only exception I could get on calling an API say..
final User me = graphClient.me().buildRequest().get();
I am getting "SocketTimeoutException"
I have read multiple documents, github threads, I am not able to understand the problem.
https://github.com/microsoftgraph/msgraph-sdk-java/issues/162
https://github.com/microsoftgraph/msgraph-sdk-java/issues/158
https://github.com/microsoftgraph/msgraph-sdk-java-core
Please help.
Your code is wrong, the /me endpoint does not support client credential flow. Modify your code and the problem should be resolved.
If you want to list users in the entire tenant, then you need to use the /users endpoint:
final Users users = graphClient.users().buildRequest().get();
If you only need to list a certain user information of tenant, then you can use the /users/{user id} endpoint:
final User user = graphClient.users("{user id}").buildRequest().get();
You need to configure proxy for Azure Identity Client too while using client credential builder as shown below
https://github.com/microsoftgraph/microsoft-graph-docs/pull/12779/commits/89729042a122ecaf4c083ccc18a9e7d565bbec25

Upload videos to Youtube from my web server in Java

My goal is to upload videos that are uploaded to my web server to Youtube on my own channel, not the users' Youtube account (my web server is acting as a proxy).
I found the sample code for uploading video to Youtube here with the credential acquired this way. The problem that I have with this sample is that it writes to disk the credential, and it opens an http server. Since my web server can potentially have a lot of users uploading their videos concurrently, the credential file location has to be dynamic, and multiple binding to the same http port is not possible. Further more, after searching through other writing about uploading to Youtube, I think this approach is for users uploading to their Youtube account.
Could you share your experiences/code sample/solutions for my scenario? In short I am just trying to automate the process of me opening up Youtube dashboard, and uploading videos to a channel in my Youtube.
In general, starting at API V3, Google prefers OAuth2 over other mechanism, and uploading a video (or any other action that modifies user data) requires OAuth2.
Fortunately, there is a special kind of token called refresh token to the rescue. Refresh token does not expire like normal access token, and is used to generate normal access token when needed. So, I divided my application into 2 parts:
The 1st part is for generating refresh token, which is a Java desktop app, meant to be run by a user on a computer. See here for sample code from Google.
The 2nd part is is part of my web application, which uses a given refresh token to create a credential object.
Here is my implementation in Scala, which you can adapt to Java version easily:
For generating a refresh token, you should set the accessType to offline for the authorization flow. Note: if a token already exists on your system, it won't try to get new token, even if it does not have refresh token, so you also have to set approval prompt to force:
def authorize(dataStoreName: String, clientId: String, clientSecret: String): Credential = {
val builder = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT,
JSON_FACTORY,
clientId,
clientSecret,
Seq(YouTubeScopes.YOUTUBE_UPLOAD)
)
val CREDENTIAL_DIRECTORY = s"${System.getProperty("user.home")}/.oauth-credentials"
val fileDataStoreFactory = new FileDataStoreFactory(new java.io.File(CREDENTIAL_DIRECTORY))
val dataStore: DataStore[StoredCredential] = fileDataStoreFactory.getDataStore(dataStoreName)
builder.setCredentialDataStore(dataStore).setAccessType("offline").setApprovalPrompt("force")
val flow = builder.build()
val localReceiver = new LocalServerReceiver.Builder().setPort(8000).build()
new AuthorizationCodeInstalledApp(flow, localReceiver).authorize("user")
}
val credential = authorize(dataStore, clientId, clientSecret)
val refreshToken = credential.getRefreshToken
For using the refresh token on the server, you can build a credential from a refresh token:
def getCredential = new GoogleCredential.Builder()
.setJsonFactory(JSON_FACTORY)
.setTransport(HTTP_TRANSPORT)
.setClientSecrets(clientId, clientSecret)
.build()
.setRefreshToken(refreshToken)
I have have bypassed the whole AuthorizationCodeInstalledApp authorize() method and created a new subclass which bypasses the jetty server implementation process.
The methods are as follows
getAuthorizationFromStorage : Get access token from stored credentials.
getAuthorizationFromGoogle : Get the authentication with the credentials from Google creates the url that will lead the user to the authentication page and creating a custom defined name-value pair in the state parameter. The value should be encoded with base64 encoder so we can receive the same code redirected from google after authentication.
saveAuthorizationFromGoogle : Save the credentials that we get from google.
Create the GoogleAuthorizationCodeFlow object from the credentialDatastorfrom the response received from the google after authentication.
Hit google to get the permanent refresh-token that can be used to get the accesstoken of the user any time .
Store the tokens like accesstoken and refreshtoken in the filename as userid
Checkout the code Implementation here

google oauth fails fetching redirect url

I'm using Google OAuth2 client-side to authorize a web-app (Liferay Portlet) to use the Calendar Service.
On my Development Server, the whole flow completes successfully:
I start creating a GoogleAuthorizationCodeRequestUrl
Using com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver I create a new redirect URI and set it to wait for Google Response.
a new window/tab opens in user's browser, to googleAuthorizationCodeRequestUrl
user Logs in (if not already logged)
User authorizes the requested scopes
the tab closes automatically, the jetty's Callback URI is fetched from Google,
The flow continues with token exchange etc
But when Deploying on some other remote server (identical environment) the flow gets stuck in step 6. Google seems to be unable to find the redirect_uri. My browsers are landing on an error page, informing that they couldn't establish a connection to the server at localhost:[random port generated from jetty]
Checking the logs, I can see that in both cases (dev/remote server), the redirect_uri created by jetty is localhost:[5digits]/Callback. (not affected by the dns or ip on the remote server) Is this normal ? Did I miss something on the configuration? Maybe jetty was supposed to create another redirect URI, that I should additionally add from Google Dev Console (obviously I cant set to localhost..)
Is it possible that a firewall or proxy setting is blocking the redirect_url?
Any other ideas what I did wrong?
EDIT: posting some code for the URLs creation
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(httpTransport, jsonFactory, secrets, scopes)
.setDataStoreFactory(dataStore).build();
Credential credents = flow.loadCredential(usermail);
String redirect_url = null;
// Checking if the given user is not authorized
if (credents == null) {
// Creating a local receiver
LocalServerReceiver receiver = new LocalServerReceiver();
try {
// Getting the redirect URI
String redirectUri = receiver.getRedirectUri();
// Creating a new authorization URL
AuthorizationCodeRequestUrl authorizationUrl = flow.newAuthorizationUrl();
// Setting the redirect URI
authorizationUrl.setRedirectUri(redirectUri);
// Building the authorization URL
String url = authorizationUrl.build();
// Logging a short message
log.info("Creating the authorization URL : " + url);
//This url will be fetched right after, as a button callback (target:_blank)
//by using :FacesContext.getCurrentInstance().getExternalContext().redirect(googleAuthUrl);
googleAuthUrl = url;
// Receiving authorization code
String code = receiver.waitForCode();
// Exchanging it for an access token
TokenResponse response = flow.newTokenRequest(code).setRedirectUri(redirectUri).execute();
// Storing the credentials for later access
credents = flow.createAndStoreCredential(response, id);
} finally {
// Releasing resources
receiver.stop();
}
}
// Setting up the calendar service client
client = new com.google.api.services.calendar.Calendar.Builder(httpTransport, jsonFactory, credents).setApplicationName(APPLICATION_NAME)
.build();
Instead of creating an instance of LocalServerReceiver by this:
// Creating a local receiver
LocalServerReceiver receiver = new LocalServerReceiver();
You should do this by using a LocalServerReceiver.Builder.
According to documentation non-parameter constructor is: Constructor that starts the server on "localhost" selects an unused port.
So you can use builder, set proper host name (remote server) and build LocalServerReceiver instance. (or you can use LocalServerReceiver(host, port) constructor)
This should set redirect_uri to proper address.

Categories