Non-interactive authorization with Google OAuth2 - authentication

I have created a Java application that performs syncing of MS Active Directory with Google Groups. It is non-interactive application that suppose to be executed by the cronjob at night time.
It does work perfectly on my laptop (DEV environment).
My problem is that on the first run it pops up browser window with the dialog asking to authorize access to the Google API. After I click on "Allow" button it happily proceeds to the end.
Now I need to move it to production server which runs CentOS and does not have a browser.
When I run my application in this environment it prints the following message:
Please open the following address in your browser:
https://accounts.google.com/o/oauth2/auth?access_type=offline&client_id=520105804541-c7d7bfki88qr8dbkv6oahp22i3oq1jq0.apps.googleusercontent.com&redirect_uri=http://localhost:50089/Callback&response_type=code&scope=https://www.googleapis.com/auth/admin.directory.group.member%20https://www.googleapis.com/auth/admin.directory.orgunit%20https://www.googleapis.com/auth/admin.directory.device.mobile.action%20https://www.googleapis.com/auth/admin.directory.orgunit.readonly%20https://www.googleapis.com/auth/admin.directory.userschema%20https://www.googleapis.com/auth/admin.directory.device.chromeos%20https://www.googleapis.com/auth/admin.directory.notifications%20https://www.googleapis.com/auth/admin.directory.device.chromeos.readonly%20https://www.googleapis.com/auth/admin.directory.device.mobile%20https://www.googleapis.com/auth/admin.directory.group.readonly%20https://www.googleapis.com/auth/admin.directory.group.member.readonly%20https://www.googleapis.com/auth/admin.directory.userschema.readonly%20https://www.googleapis.com/auth/admin.directory.user.alias%20https://www.googleapis.com/auth/admin.directory.user.security%20https://www.googleapis.com/auth/admin.directory.device.mobile.readonly%20https://www.googleapis.com/auth/admin.directory.user%20https://www.googleapis.com/auth/admin.directory.user.readonly%20https://www.googleapis.com/auth/admin.directory.user.alias.readonly%20https://www.googleapis.com/auth/admin.directory.group%20https://www.googleapis.com/auth/apps.groups.settings
There is no browser on this machine but if I try to run it on another box I am getting error 400 - invalid request.
The reason is clear, because it redirecting to localhost:50089 and nobody is listening on that port on another machine.
Going through the multiple documents and samples on developers.google.com I found a way to bypass the dialog by creating the service account and generating a primary key for it.
try {
GoogleCredential cr = GoogleCredential
.fromStream(new FileInputStream(keyFile))
.createScoped(SCOPES);
Directory directory = new Directory.Builder(httpTransport, jsonFactory, cr)
.setApplicationName(config.getProperty("google.application.name")).build();
} catch (IOException ex) {
LOG.error("Failed to establish access credentials : ", ex.getMessage());
}
It does read the key and does create Directory instance, but any request to it results in 403 response code
{ "code" : 403, "errors" : [ { "domain" : "global", "message" :
"Not Authorized to access this resource/api", "reason" : "forbidden"
} ], "message" : "Not Authorized to access this resource/api" }"
But I have delegated all the privileges for this account to the service account. Don't know what else I can do.
If anyone can help me out of this jam, I appreciate it greatly.

I found solution. The problem is that Google generated JSON key does not set service account user. You have to do it manually like this:
GoogleCredential cr = GoogleCredential
.fromStream(new FileInputStream(keyFile))
.createScoped(SCOPES);
GoogleCredential.Builder builder = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountScopes(SCOPES)
.setServiceAccountId(cr.getServiceAccountId())
.setServiceAccountPrivateKey(cr.getServiceAccountPrivateKey())
.setServiceAccountPrivateKeyId(cr.getServiceAccountPrivateKeyId())
.setTokenServerEncodedUrl(cr.getTokenServerEncodedUrl())
.setServiceAccountUser(user);
Directory directory = new Directory.Builder(
httpTransport, jsonFactory, builder.build())
.setApplicationName(config.getProperty("google.application.name")).build();

Related

Authentication Service throws Procedure invocation error post upgrade from MFP 6.3 to MFP 7.1

Authentication adapter throwing "Procedure invocation error" sometimes. Tried clearing cache and cookies but still the same. So we tried to login from different system for same user and it works. This is quite confusing as once we try with different ID in browser where issue occurred, it works and then it works with Member ID which has issue as well. Auth required is not coming in response when issue occurs.
we have tried to look into logs and found WorklightAuthenticationException from Authentication Adapter while trying security test procedure.
Authentication Adapter code:
var result = WL.Server.invokeHttp(input);
WL.Logger.info("Authentication service : " + JSON.stringify(result));
authResponse = prepareJSONResponse(result,channelId);
WL.Logger.info('Formatted response -> ' + JSON.stringify(authResponse));
if(result.isSuccessful == false){
WL.Logger.info("Error: " + result.errorMessage);
return onAuthRequired(null, "Error in connecting to server. Please try again later.");
}
if(typeof authResponse.errorMessage != 'undefined'){
WL.Logger.info("Error is defined" +authResponse.errorMessage);
return onAuthRequired(null, authResponse);
}
WL.Logger.info("Authentication service success: " + JSON.stringify(result));
WL.Logger.info("userIdentity Parameters: " + inputParams.CorpId);
var userIdentity = {
userId: inputParams.CorpId,
displayName: inputParams.CorpId,
attributes: {
foo: "bar"
}
};
WL.Logger.info("userIdentity::"+JSON.stringify(userIdentity));
WL.Server.setActiveUser("SingleStepAuthRealm", userIdentity);
return {
authRequired: false
};
It is happening due to the requests going from one node to another node. Handled it in Load balancer to send requests to specific node based on cookies and post that it works fine.
The description mentions about clearing cache and cookies and using browser.
Browser based environments are not supported in session independent mode. These work only in session dependent mode. As such, it is imperative that session based affinity be enabled to ensure the requests land in the same JVM for authentication state to be preserved.
More details can be found here : Session-independent mode

Issue with SSH.NET UWP

I am having issues using SSh.net in a UWP App. This app will run on Win10.
I get the following error:
An attempt was made to access a socket in a way forbidden by its access permissions. I have looked online and there is no one actually dealing with this.The exact same code works in a standard Desktop App (WPF)
The key is the key string and I had to replace \r with \n because the PrivateKeyFile creation gave an error message and I tracked this down to carriage return placed instead of new line (by the textbox).
key = key.Replace("\r", "\n");
PrivateKeyFile(stringToStream(key));
client = new SshClient(ip, port, username, pkf);
if (!client.IsConnected)
{
try
{
client.Connect();
connected = true;
}
catch (Exception ex)
{
exception = ex.Message.ToString();
connected = false;
}
}
Finally resolved this issue - Look here: An attempt was made to access a socket in a way forbidden by its access permissions
Go down to the following line:
If you're getting the same error in Windows 8 development, it could be that you haven't enabled access over Private Networks in your Package.appxmanifest file:
Select the Private Networks (Client & Server) option as shown on the image.
Click here for the image

Google analytics integration in mvc4

try
{
UserCredential credential;
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets { ClientId = ClientID, ClientSecret = ClientSecret },
new[] { AnalyticsService.Scope.AnalyticsReadonly, AnalyticsService.Scope.AnalyticsEdit },
"user",
CancellationToken.None,
new FileDataStore("Analytics.Auth.Store")).Result;
return credential;
}
catch { return null; }
I am using above code for google console web application(Google Analytic) but it gives redirect_uri mismatch error. How i can send redirect_uri.
redirect_uri is set up in the Google Developers console -> apis & auth -> credentials
Not sure if Sanaan C ever found an answer ... the reason that your code does not work in a web application is likely because the user that created the Analytics.Auth.Store entry in that user's %APPDATA% folder is NOT the one running your web application.
Does anyone have a solution to this - and please excuse that this question is appended to another ... I actually think this was the intended question in any event ...
===
One simple-minded solution could be to take the credentials created by a user who can respond to the redirect and put it in a folder, with appropriate access permissions, where the user under which the IIS service is being run can find it. Instantiate the FileDataStore with a full path to this folder ...

ArgumentException: Precondition failed.: !string.IsNullOrEmpty(authorization.RefreshToken) with Service Account for Google Admin SDK Directory access

I'm trying to access the Google Directory using a Service Account. I've fiddled with the DriveService example to get this code:
public static void Main(string[] args)
{
var service = BuildDirectoryService();
var results = service.Orgunits.List(customerID).Execute();
Console.WriteLine("OrgUnits");
foreach (var orgUnit in results.OrganizationUnits)
{
Console.WriteLine(orgUnit.Name);
}
Console.ReadKey();
}
static DirectoryService BuildDirectoryService()
{
X509Certificate2 certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret",
X509KeyStorageFlags.Exportable);
var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
{
ServiceAccountId = SERVICE_ACCOUNT_EMAIL,
Scope = DirectoryService.Scopes.AdminDirectoryOrgunit.GetStringValue()
};
var auth = new OAuth2Authenticator<AssertionFlowClient>(provider, AssertionFlowClient.GetState);
return new DirectoryService(new BaseClientService.Initializer()
{
Authenticator = auth,
ApplicationName = "TestProject1",
});
}
When I run it, I get
ArgumentException: Precondition failed.: !string.IsNullOrEmpty(authorization.RefreshToken)
I'm going round in circles in the Google documentation. The only stuff I can find about RefreshTokens seems to be for when an individual is authorizing the app and the app may need to work offline. Can anyone help out or point me in the direction of the documentation that will, please.
Service Account authorization actually do not return Refresh Token - so this error makes sense. Do you know where this is coming from?
I am not too familiar with the .NET client library but having the full error trace would help.
As a longshot - The error might be a bad error -
Can you confirm that you've enabled the Admin SDK in the APIs console for this project
Can you confirm that you whitelisted that Client ID for the service account in the domain you are testing with (along with the Admin SDK scopes)
The above code will work if you replace the provider block with:
var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
{
ServiceAccountId = SERVICE_ACCOUNT_EMAIL,
Scope = DirectoryService.Scopes.AdminDirectoryOrgunit.GetStringValue(),
ServiceAccountUser = SERVICE_ACCOUNT_USER //"my.admin.account#my.domain.com"
};
I had seen this in another post and tried it with my standard user account and it didn't work. Then I read something that suggested everything had to be done with an admin account. So, I created a whole new project, using my admin account, including creating a new service account, and authorising it. When I tried it, it worked. So, then I put the old service account details back in but left the admin account in. That worked, too.

Google OAuth2 Service Accounts API Authorization

I'm trying to authenticate my server app through Google's service account authentication but, for some reason, it is just not pushing through.
In the API console, I already created the project, enabled the service I need (Admin SDK), and created a Service Account and Web Application API Access.
When I do use the web application access credentials I am able to authenticate and retrieve user records. But using service account authentication would keep giving me a login required message.
"error": { "errors": [ { "domain": "global", "reason": "required", "message": "Login Required", "locationType": "header", "location": "Authorization" } ], "code": 401, "message": "Login Required" }
I forgot to add, I am testing this with the PHP client library.
public function init() {
$client = new Google_Client();
if (isset($_SESSION['access_token'])) {
$client->setAccessToken($_SESSION['access_token']);
}
$key = file_get_contents(App::SERVICE_KEY_FILE);
$client->setAssertionCredentials(new Google_AssertionCredentials(
App::SERVICE_ACCOUNT_NAME,
App::SERVICE_API_SCOPES,
$key)
);
$client->setClientId(App::SERVICE_CLIENT_ID);
debug($client, 'CLIENT');
if ($client->getAccessToken()) {
$this->access_token = $_SESSION['access_token'] = $client->getAccessToken();
debug($_SESSION['access_token'], 'TOKEN');
} else {
debug('NO TOKEN');
}
$this->client = $client;
}
As you can see, the code is basically about the same as the Google example. Am I missing an extra step?
One last thing, when I authenticate using the web app then access my service account script, the service account script can pick up the web app script's session and push through with the user record retrievals. Does that mean the Admin SDK API explicitly needs user interaction through web app authentication?
Instead of service account, I instead opted to use installed applications API Access.
This ruby gem actually helped my figure this out - https://github.com/evendis/gmail_cli
I was playing with it on the console and just followed the authorization steps in the readme, and found that installed applications is more simple when doing server admin apps.
Being a newb, I think I missed the important part the refresh token plays in the entire process. Going via the installed application approach helped me figure that out.
My config file now contains the client id, client secret, api scope, redirect uri, authorization code, and the refresh token; my initialization code now looks like:
public function init() {
$client = new Google_Client();
$client->setClientId(App::CLIENT_ID);
$client->setClientSecret(App::CLIENT_SECRET);
$client->setScopes(App::API_SCOPES);
$client->setRedirectUri(App::REDIRECT_URI);
if (!$client->getAccessToken()) {
$client->refreshToken(App::REFRESH_TOKEN);
}
$this->access_token = $client->getAccessToken();
$this->client = $client;
}