Google API for getting maximum number of licenses in a Google Apps domain - api

I have a Google Apps Script function used for setting up accounts for new employees in our Google Apps domain.
The first thing it does is makes calls to the Google Admin Settings API and retrieves the currentNumberOfUsers and maximumNumberOfUsers, so it can see if there are available seats (otherwise a subsequent step where the user is created using the Admin SDK Directory API would fail).
It's been working fine until recently when our domain had to migrate from Postini to Google Vault for email archiving.
Before the migration, when creating a Google Apps user using the Admin SDK Directory API, it would increment the currentNumberOfUsers by 1 and the new user account user would automatically have access to all Google Apps services.
Now after the migration, when creating a Google Apps user, they aren't automatically assigned a "license," so I modified my script to use the Enterprise License Manager API and now it assigns a "Google-Apps-For-Business" license. That works fine.
However, the currentNumberOfUsers is now different from the number of assigned licenses, and "Google-Apps-For-Business" is only one of several different types of licenses available.
I can get the current number of assigned "Google-Apps-For-Business" licenses by running this:
var currentXml = AdminLicenseManager.LicenseAssignments.listForProductAndSku('Google-Apps', 'Google-Apps-For-Business', 'domain.com', {maxResults: 1000});
var current = currentXml.items.toString().match(/\/sku\/Google-Apps-For-Business\/user\//g).length;
But the number that produces is different from currentNumberOfUsers.
All I really need to do now is get the maximum number of owned "Google-Apps-For-Business" licenses so the new employee setup script can determine whether there are any available.
I checked the API Reference documentation for the following APIs but...
Enterprise License Manager API → Doesn't have a method for getting the maximum or available number of licenses.
Google Admin Settings API → Doesn't deal with licenses, only "users."
Admin SDK Directory API User resource → Doesn't deal with licenses.
Google Apps Reseller API → This API seems to have what I need, but it's only for Reseller accounts.
I know I can program my new employee setup script to just have a try/catch seeing if it would be able to create the user and assign the license, and end the script execution gracefully if it can't, but that doesn't seem efficient.
Also, part of the old script was that if there were less than X seats available, it would email me a heads-up to order more. I can program a loop that attempts to repeatedly create dummy users and assign them licenses and count the number of times it can do that before it fails, then delete all the dummy users, but, once again, that's not efficient at all.
Any ideas?
Update 3/11/2020: Since the Admin Settings API had shut down a few years ago I've been using the Enterprise License Manager API to get the current number of used licenses, like this:
function getCurrentNumberOfUsedGoogleLicenses(skuId) {
var success = false, error = null, count = 0;
var adminEmail = 'admin#domain.com';
var gSuiteDomain = adminEmail.split('#')[1];
// for more information on the domain-wide delegation:
// https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority
// the getDomainWideDelegationService() function uses this:
// https://github.com/gsuitedevs/apps-script-oauth2
var service = getDomainWideDelegationService('EnterpriseLicenseManager: ', 'https://www.googleapis.com/auth/apps.licensing', adminEmail);
if (skuId == 'Google-Apps-Unlimited') var productId = 'Google-Apps';
else return { success: success, error: "Unsupported skuId", count: count };
var requestBody = {};
requestBody.headers = {'Authorization': 'Bearer ' + service.getAccessToken()};
requestBody.method = "GET";
requestBody.muteHttpExceptions = false;
var data, pageToken, pageTokenString;
var maxAttempts = 5;
var currentAttempts = 0;
var pauseBetweenAttemptsSeconds = 3;
loopThroughPages:
do {
if (typeof pageToken === 'undefined') pageTokenString = "";
else pageTokenString = "&pageToken=" + encodeURIComponent(pageToken);
var url = 'https://www.googleapis.com/apps/licensing/v1/product/' + productId + '/sku/' + skuId + '/users?maxResults=1000&customerId=' + gSuiteDomain + pageTokenString;
try {
currentAttempts++;
var response = UrlFetchApp.fetch(url, requestBody);
var result = JSON.parse(response.getContentText());
if (result.items) {
var licenseAssignments = result.items;
var licenseAssignmentsString = '';
for (var i = 0; i < licenseAssignments.length; i++) {
licenseAssignmentsString += JSON.stringify(licenseAssignments[i]);
}
if (skuId == 'Google-Apps-Unlimited') count += licenseAssignmentsString.match(/\/sku\/Google-Apps-Unlimited\/user\//g).length;
currentAttempts = 0; // reset currentAttempts before the next page
}
} catch(e) {
error = "Error: " + e.message;
if (currentAttempts >= maxAttempts) {
error = 'Exceeded ' + maxAttempts + ' attempts to get license count: ' + error;
break loopThroughPages;
}
} // end of try catch
if (result) pageToken = result.nextPageToken;
} while (pageToken);
if (!error) success = true;
return { success: success, error: error, count: count };
}
However, there still does not appear to be a way to get the maximum number available to the domain using this API.

Use CustomerUsageReports.
jay0lee is kind enough to provide the GAM source code in Python. I crudely modified the doGetCustomerInfo() function into Apps Script thusly:
function getNumberOfLicenses() {
var tryDate = new Date();
var dateString = tryDate.getFullYear().toString() + "-" + (tryDate.getMonth() + 1).toString() + "-" + tryDate.getDate().toString();
while (true) {
try {
var response = AdminReports.CustomerUsageReports.get(dateString,{parameters : "accounts:gsuite_basic_total_licenses,accounts:gsuite_basic_used_licenses"});
break;
} catch(e) {
//Logger.log(e.warnings.toString());
tryDate.setDate(tryDate.getDate()-1);
dateString = tryDate.getFullYear().toString() + "-" + (tryDate.getMonth() + 1).toString() + "-" + tryDate.getDate().toString();
continue;
}
};
var availLicenseCount = response.usageReports[0].parameters[0].intValue;
var usedLicenseCount = response.usageReports[0].parameters[1].intValue;
Logger.log("Available licenses:" + availLicenseCount.toString());
Logger.log("Used licenses:" + usedLicenseCount.toString());
return availLicenseCount;
}

I would recommend exploring GAM which is a tool that gives command line access to the administration functions of your domain.

Related

The service is not available received when calling MS Graph API

We are doing a MS Graph API call to get the Sharepoint URL of a Team.
API URL: GET https://graph.microsoft.com/v1.0/groups/{GroupID}/sites/root/weburl
We get this :
Response:
{
"error": {
"code": "serviceNotAvailable",
"message": "The service is not available. Try the request again after a delay. There may be a Retry-After header.",
"innerError": {
"request-id": "9f23d067-e851-4c43-8701-abe137683b87",
"date": "2020-03-05T13:53:43"
}
}
}
What could be the issue?
I have been experiencing a similar problem in searching sites ( GET /sites?search=* ) with the Graph API since March 2nd. I have not been able to recover. I have experienced this over multiple O365 tenants, both free and licensed.
Microsoft docs say this error code is due to MSFT induced throttling, but my request rate is like 50 per hour.
This seems to be a Microsoft bug. I posted a stack overflow issue for this and #rafa-ayadi reported that MSFT was fixing it their side for one of his customers.
I bought an Azure Developer Support subscription for this issue, but MSFT closed it and referred me to Sharepoint Developer Support, for which I can find no link or pricing. So no luck yet in getting MSFT to acknowledge and fix for me.
/**
You need do authentication delegated. See the follow code:
First of all you need from portal.azure.com register app and get:
folder id it is tenantID
App Id. it is clientId
**/
URL urlObj = new
URL("https://login.microsoftonline.com/"+config.tenantID+"/oauth2/v2.0/token");
HttpURLConnection httpCon = (HttpURLConnection) urlObj.openConnection();
String urlParameters = "" + // para la v2.0
"grant_type"+"="+"password"+"&"+ /
"scope" + "=" + "https%3A%2F%2Fgraph.microsoft.com%2F.default" +"&" +
"client_id" + "=" + config.clientId +"&" +
"client_secret" + "=" + config.clientSecret +"&" +
"username" + "=" + config.username +"&" +
"password" + "=" + config.contrasena +"&";
byte[] postData = urlParameters.getBytes( StandardCharsets.UTF_8 );
int postDataLength = postData.length;
httpCon.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
httpCon.setRequestProperty("Content-Length",String.valueOf(postDataLength));
httpCon.setRequestMethod("POST");
httpCon.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(httpCon.getOutputStream());
writer.write(urlParameters);
writer.flush();
int status = httpCon.getResponseCode();
BufferedReader in = new BufferedReader(new
InputStreamReader(httpCon.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
String body = getBody(content.toString());
String token = null;
final ObjectNode node = new ObjectMapper().readValue(body, ObjectNode.class);
if (node.has("access_token")) {
token = node.get("access_token").asText();
}
httpCon.disconnect();
return token;
My similar problem accessing any resource in the sites API was caused by having both the Groups.Create and Groups.ReadWrite.All permissions granted at the same time for application type access.
Removing Groups.Create allowed the all CRUD calls to be successful without serviceNotAvailable errors, even command line calls that just access sites.
Be sure to update admin grant and your token if you change the permissions for a test.
User #user13034886 mentioned the permission clash in another post.

Crunchbase Data API v3.1 to Google Sheets

I'm trying to pull data from the Crunchbase Open Data Map to a Google Spreadsheet. I'm following Ben Collins's script but it no longer works since the upgrade from v3 to v3.1. Anyone had any luck modifying the script for success?
var USER_KEY = 'insert your API key in here';
// function to retrive organizations data
function getCrunchbaseOrgs() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Organizations');
var query = sheet.getRange(3,2).getValue();
// URL and params for the Crunchbase API
var url = 'https://api.crunchbase.com/v/3/odm-organizations?query=' + encodeURI(query) + '&user_key=' + USER_KEY;
var json = getCrunchbaseData(url,query);
if (json[0] === "Error:") {
// deal with error with fetch operation
sheet.getRange(5,1,sheet.getLastRow(),2).clearContent();
sheet.getRange(6,1,1,2).setValues([json]);
}
else {
if (json[0] !== 200) {
// deal with error from api
sheet.getRange(5,1,sheet.getLastRow(),2).clearContent();
sheet.getRange(6,1,1,2).setValues([["Error, server returned code:",json[0]]]);
}
else {
// correct data comes back, filter down to match the name of the entity
var data = json[1].data.items.filter(function(item) {
return item.properties.name == query;
})[0].properties;
// parse into array for Google Sheet
var outputData = [
["Name",data.name],
["Homepage",data.homepage_url],
["Type",data.primary_role],
["Short description",data.short_description],
["Country",data.country_code],
["Region",data.region_name],
["City name",data.city_name],
["Blog url",data.blog_url],
["Facebook",data.facebook_url],
["Linkedin",data.linkedin_url],
["Twitter",data.twitter_url],
["Crunchbase URL","https://www.crunchbase.com/" + data.web_path]
];
// clear any old data
sheet.getRange(5,1,sheet.getLastRow(),2).clearContent();
// insert new data
sheet.getRange(6,1,12,2).setValues(outputData);
// add image with formula and format that row
sheet.getRange(5,2).setFormula('=image("' + data.profile_image_url + '",4,50,50)').setHorizontalAlignment("center");
sheet.setRowHeight(5,60);
}
}
}
This code no longer pulls data as expected.
I couldn't confirm about the error messages when you ran the script. So I would like to show about the clear difference point. It seems that the endpoint was changed from https://api.crunchbase.com/v/3/ to https://api.crunchbase.com/v3.1/. So how about this modification?
From :
var url = 'https://api.crunchbase.com/v/3/odm-organizations?query=' + encodeURI(query) + '&user_key=' + USER_KEY;
To :
var url = 'https://api.crunchbase.com/v3.1/odm-organizations?query=' + encodeURI(query) + '&user_key=' + USER_KEY;
Note :
From your script, I couldn't also find query. So if the script doesn't work even when you modified the endpoint, please confirm about it. You can see the detail of API v3 Compared to API v3.1 is here.
References :
API v3 Compared to API v3.1
Using the API
If this was not useful for you, I'm sorry.

Fetch users liked Instagram-images after API update 2016

Hey!
After Instagram updated its API on the 1. june 2016 we are not able to get out JavaScript Instagram implementation to work. We inspected the client-setup and noticed that it has been put in Sandbox mode. When trying to resubmit then none of the valid user cases did fit, and we understood that they had removed our rights to fetch other peoples content through the account without getting permission from each individual owner.
As far as i understood, the new parameter "scope" needs to be set to "public_content" and this requires the users permission.
So at the moment the solution below only fetches the users own pictures that is self-liked. Before we also got other peoples images that the user had liked.
So, do anyone know how to fetch liked content of an account post 1. june 2016?
Does getting a Instagram Business account help you for displaying other peoples content?
Here is the code example with an additional filter on the hashtags.
If anyone got a better idea of how to filter on hashtags and liked content at the same time please let me know :)
$.fn.instagramFeedInit = function()
{
if(!$('#instagram-content').length)
{
return;
}
var container = $('#instagram-content');
var tag = container.data('hashtag');
var image_display_count = container.data('image-count');
var access_token = [INSERT_TOKEN_OR_SOUTH_PARK_REFERENCE_HERE];
$.ajax({
type:'GET',
url: 'https://api.instagram.com/v1/users/self/media/liked?access_token=' + access_token + '&callback=callback&count=99999',
jsonp: 'callback',
dataType: 'jsonp',
success: function(response) {
var container = $('#instagram-content');
var i = 0;
// Loops through images
$(response.data).each(function(i, el){
// Check that it has correct tag
if(jQuery.inArray(tag, el.tags) !== -1)
{
var imageTitle = el.caption.text;
var image = el.images.low_resolution.url;
var refImage = el.images.standard_resolution.url;
if (el.type === 'video') {
var videoLink = el.videos.standard_resolution.url;
container.append('<a class="lightbox instagramimage mfp-iframe" title="' + imageTitle + '" href="' + videoLink + '" ><img src="' + image + '"></a>');
}
else {
container.append('<a class="lightbox instagramimage" title="' + imageTitle + '" href="' + refImage + '" ><img src="' + image + '" class="img-responsive"></a>');
}
i++;
// If we have reached maximum display images
if(i == image_display_count) return false;
}
});
}
});
};
Be aware that if you are in Sandbox mode, only post from authorized Sandboxed users will appear in any api requests. Regardless of whether you have the public_access scope.
Note: you need to add scope=public_access to your authorization URL now. It's no longer included by default
E.g A and B are both sandbox users C is not.
A likes a post from C.
The liked media endpoint for A will not include the post from C. He is invisible to your sandbox app
A then goes an likes a post from B
The liked media endpoint for A will now include the post from B as he is visible to your sandbox app

Eventbrite API date range parameter for organizer_list_events

I need a way to search via the eventbrite api past events, by organizer, that are private, but I also need to be able to limit the date range. I have not found a viable solution for this search. I assume the organizer_list_events api would be the preferred method, but the request paramaters don't seem to allow for the date range, and I am getting FAR too many returns.
I'm having some similar issues I posted a question to get a response about parsing the timezone, here's the code I'm using to get the dates though and exclude any events before today (unfortunately like you said I'm still getting everything sent to me and paring things out client side)
Note this is an AngularJS control but the code is just using the EventBrite javascript API.
function EventCtrl($http, $scope)
{
$scope.events=[];
$scope.noEventsDisplay = "Loading events...";
Eventbrite({'app_key': "EVC36F6EQZZ4M5DL6S"}, function(eb){
// define a few parameters to pass to the API
// Options are listed here: http://developer.eventbrite.com/doc/organizers/organizer_list_events/
//3877641809
var options = {
'id' : "3588304527",
};
// provide a callback to display the response data:
eb.organizer_list_events( options, function( response ){
validEvents = [];
var now = new Date().getTime();
for(var i = 0; i<response.events.length; i++)
{
var sd = response.events[i].event.start_date;
var ed = response.events[i].event.end_date;
var parsedSD = sd.split(/[:-\s]/);
var parsedED = ed.split(/[:-\s]/);
var startDate = new Date(parsedSD[0], parsedSD[1]-1, parsedSD[2], parsedSD[3], parsedSD[4], parsedSD[5]);
var endDate = new Date(parsedED[0], parsedED[1]-1, parsedED[2], parsedED[3], parsedED[4], parsedED[5]);
if(endDate.getTime()<now)
continue;
response.events[i].event.formattedDate = date.toDateString();
validEvents.push(response.events[i])
}
if(validEvents.length == 0)
{
$scope.$apply(function(scope){scope.noEventsDisplay = "No upcoming events to display, please check back soon.";});
}
else
{
$scope.$apply(function(scope){scope.noEventsDisplay = "";});
}
$scope.$apply(function(scope){scope.events = validEvents;});
//$('.event_list').html(eb.utils.eventList( response, eb.utils.eventListRow ));
});
});
}

How to insert rule in Google Calendar ACL from Google Apps Script

How can I add a new user to the ACL for a Google Calendar? I'm trying to send a POST HTTP request. Perhaps there is something wrong with the XML? The code below generates a server error (400). (Edit: Shows the oAuth).
//---------------------------------------------------------------
// Add a rule to the Access Control List for 'Fake Calendar 1.0'
//---------------------------------------------------------------
function addRule() {
// Get Calendar ID, script user's email, and the API Key for access to Calendar API
var calId = '12345calendar.google.com';
var userEmail = Session.getActiveUser().getEmail();
var API_KEY = 'ABC123';
var newUserEmail = 'person#example.net';
// Get authorization to access the Google Calendar API
var apiName = 'calendar';
var scope = 'https://www.googleapis.com/auth/calendar';
var fetchArgs = googleOAuth_(apiName, scope);
fetchArgs.method = 'POST';
var rawXML = "<entry xmlns='http://www.w3.org/2005/Atom' " +
"xmlns:gAcl='http://schemas.google.com/acl/2007'>" +
"<category scheme='http://schemas.google.com/g/2005#kind' " +
"term='http://schemas.google.com/acl/2007#accessRule'/>" +
"<gAcl:role value='owner'/>" +
"<gAcl:scope type='user' value='"+userEmail+"'/>" +
"</entry>";
fetchArgs.payload = rawXML;
fetchArgs.contentType = 'application/atom+xml';
// Get the requested content (the ACL for the calendar)
var base = 'https://www.googleapis.com/calendar/v3/calendars/';
var url = base + calId + '/acl?key=' + API_KEY;
var content = UrlFetchApp.fetch(url, fetchArgs).getContentText();
Logger.log(content);
}
//--------------------------------------------------------------
// Google OAuth
//--------------------------------------------------------------
function googleOAuth_(name,scope) {
var oAuthConfig = UrlFetchApp.addOAuthService(name);
oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope="+scope);
oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
oAuthConfig.setConsumerKey("anonymous");
oAuthConfig.setConsumerSecret("anonymous");
return {oAuthServiceName:name, oAuthUseToken:"always"};
}
Have you gone through the oAuth authorization process before executing this piece of code. Your app has to be explicitly authorized before it can do anything significant with the Calendar API
Srik is right. You need to use oAuth Arguments in your UrlFetchApp.
Given Reference URL shows few examples for using oAuth in Apps script to work with Google's REST APIs
https://sites.google.com/site/appsscripttutorial/urlfetch-and-oauth