How Do I Authenticate a Service Account via Apps Script in order to Call a GCP Cloud Function - google-app-engine

I'm trying hell hard to run a GCP Cloud Function from Apps Script. The Cloud function requires Authentication. However I keep getting thrown a "401 Error"
On My Google Project;
I've Created a Cloud function that requires Authentication
I've Created a Service Account that has Invoke (and edit) access to that function
I've Downloaded the JSON key for that service account and saved it as an object named CREDS in my Apps Script
This is my script so far:
const CREDS = {....JSON Key I downloaded from Cloud Console}
function base64Encode(str){
let encoded = Utilities.base64EncodeWebSafe(str)
return encoded.replace(/=+$/,'')
}
function encodeJWT(){
const privateKey = `Copied the PK from the CREDs file and replaced all the escaped whitespace as a string literal`;
let header = JSON.stringify({
alg: "RS256",
typ: "JWT",
});
let encodedHeader = base64Encode(header);
const now = Math.floor(Date.now() / 1000);
let payload = JSON.stringify({
"iss": "https://accounts.google.com",
"azp": "OAUTH CLIENT ID I CREATED ON GCP",
"aud": "OAUTH CLIENT ID I CREATED ON GCP",
"sub": CREDS.client_id,
"email": CREDS.client_email,
"email_verified": true,
// "at_hash": "TMTv8_OtKA539BBRxLoTBw", //Saw this in a reverse engineered Token but didnt know what to put
"iat": now.toString(),
"exp": (now + 3600).toString()
})
let encodedPayload = base64Encode(payload);
let toSign = [encodedHeader, encodedPayload].join('.')
let signature = Utilities.computeRsaSha256Signature(toSign, privateKey)
let encodedSignature = base64Encode(signature);
let jwt = [toSign, encodedSignature].join('.')
return jwt;
}
function testFireStore() {
let funcUrl = "https://[MY PROJECT].cloudfunctions.net/MyFunc"
const token = encodeJWT()
let options = {
headers:{
"Authorization": "Bearer " + token
}
}
let func = UrlFetchApp.fetch(funcUrl,options)
Logger.log(func.getContentText())
}
The actual Cloud func just gives a "Hello World" for now and it tests fine in the console
FYI, some steps I've done already
I've generated a token using gcloud on my local machine and used it in my apps script, that works fine
I've taken the said token and reverse engineered it on https://jwt.io
I've used the code here to create my JWT function which I checked back with https://jwt.io to ensure its in the correct format.

This Solution posted by #TheMaster in the comments to my solution solved the issue.
On the GCP side, I went in and enabled compute Engine and App Engine, then used this solution and it worked.
The only odd thing is that the target_audience requested there, I had to do a bit of reverse engineering to get it. I had to get the Identity Token from the command line tool, then use jwt.io to decode it, getting the AUD key...
but that aside, everythign worked like a charm

Related

How to get User Access Token claims for Facebook registered app

I am used to working with Azure Active Directory. I know that if I register an app, I can use MSALs library in React (which uses OIDC in the background) to get a JWT Token (I actually get 2, an ID and an Access Token, but that is beyond the scope of this question). I can then make a call from my front end to my back end API, and send that token through as authentication. In the backend, I check the signature, and also check that the app of the token is the app that I registered, and also that the token is generated for use 'X'. So with that single token I can protect my backend API and make sure that the frontend call is legitimately coming from the SPA (unless the token gets stolen), and that the user 'X' is the one whose session is active for the given API call.
Now, for a personal project, I am trying to work with Facebook as an identity provider. I registered my app in facebook, and in my freshly created react project I have:
<React.StrictMode>
<FacebookLogin
appId="<App ID Here>"
autoLoad={false}
fields="name,email,picture"
callback={responseFacebook}
/>
<App />
</React.StrictMode>
and
const responseFacebook = (response) => {
console.log(response);
}
Now, when I click the button, I see the following in the console:
{
"name": "<My Name Here>",
"email": "<My Mail Here>",
"picture": "<Picture Data Here>",
"id": "<ID Here>",
"accessToken": "EAAJlEXBfRWgBAGHTP0ZAgRttMSMZzAxWM4B6aAZBL7FlGSWgZBITj9HXb2mZAVuoyIc1V43StjYwuwcsSTZBEd4aPK6iGwbNGRvN7o5PH3ZAOymGxtb5W8k2BYYdAW5w1frXt8JeEvJI3SCoOsJqMVHm9mo5N7NpSZBKs74wqu8LvgQbqqYfvialeSP5LwtUMMS51pMsN5Kwv7aWPud0",
"userID": "<User ID Here>",
"expiresIn": 6488,
"signedRequest": "<A Bunch of Gibberish>",
"graphDomain": "facebook",
"data_access_expiration_time": 1677528712
}
Now, about the access token, that is no JWT and I dont know how to use it for authenticating against my OWN backend API. I know I can use that token to make requests to facebook GraphAPI and get more info about the user (in this case, me). But, when I make a request to my backend, and I send that token, I have no clue how to verify its a legitimate token, meaning:
Verify its signed by facebook
Verify the APP ID is the one im looking for
Verify for which user was the token generated
In good old JWT, I can simply check the claims, but I have no idea what this is and how to prove to a backend server, or any third party for that matter, that the token was generated by 'X' user interacting with 'Y' app registered on facebook
Well, as per #CBroe's suggestion, I used the debug token Graph endpoint to retrieve info about the token (Doc https://developers.facebook.com/docs/graph-api/reference/v15.0/debug_token).
So in the front end, I get the info shown in the question, and I need to pass that info to a backend API, as well as proof of said info legitimacy. So, in pseudocode:
// Frontend
body = {
"name": "<My Name Here>",
"email": "<My Mail Here>",
"picture": "<Picture Data Here>",
"id": "<ID Here>",
"accessToken": "EAAJlEXBfRWgBAGHTP0ZAgRttMSMZz...",
"userID": "<User ID Here>",
"expiresIn": 6488,
"signedRequest": "<A Bunch of Gibberish>",
"graphDomain": "facebook",
"data_access_expiration_time": 1677528712
};
RestClient.call("myapiurl/myendpoint", "HTTP_METHOD", body=body);
and
// Backend
function checkValidity(info) {
version = "v15.0"
token = info.accessToken;
endpointURL = "https://graph.facebook.com/${version}/debug_token?input_token=${token}";
headers = { "Authorization": "Bearer ${token}" };
response = RestClient.call(endpointURL, "GET", headers=headers);
// Note that the token is sent twice, once for Auth purposes, and another time as payload for the debug endpoint
/*
The response will look like:
{
"data": {
"app_id": "<APP ID>",
"type": "USER",
"application": "<APP Name>",
"data_access_expires_at": 1677601862,
"expires_at": 1669831200,
"is_valid": true,
"scopes": [
"email",
"public_profile"
],
"user_id": "<USER ID>"
}
}
*/
if (response.data.app_id == "<MY APP ID>" && response.data.user_id == info.userID) {
return true;
} else {
return false;
}
}
app.post('/myendpoint', function(req, res) {
checkValidity(req.body);
res.send('some result');
});
This way we can be sure that the token is valid (as if it wasn't, the facebook endpoint would have returned an error. Also, we can confirm the token was generated by userID for appID. In your backend data system you need to use the userID as your main reference, because its the only guaranteed value to have to map that user's activity in that appID (unless the user allows the app to read the email).
Note that the userID is different for each user-app pair, so if you want data such as the user email, you need to use the token to hit facebook's /user endpoint with that userID to get the info you need.
You should not trust the email field sent in the body because, since the AppID is public as its dealt with in the frontend, someone could make up a mock frontend and login with their own account (therefore generating a valid token with its matching userID) and forge the rest of the fields in the body.
This assumes the token has not been stolen (as with any bearer token solution), and that the user is careful and aware of where he/she is signing into, (as a malicious phishing clone frontend could trick the user into SSOing into a fake site).

Unity/Android ServerAuthCode has no idToken on Backend

I have an unity app and use the google-play-games plugin with google *.aar versions 9.4.0. I lately changed my Backend (Google App Engine) from php to java. My problem is the following: in php the serverauthcode is used to get the users data (in JWT format) - it was working fine. So I changed to a Java servlet and I am failing since 2 days to get a valid idtoken. I am able to recieve the server auth code from my app and a valid token response is made by GoogleAuthorizationCodeTokenRequest (see code snippet). Unfortunately it does not contain any idtoken content but a valid auth_token. So I can not get the user id to identifiy the user. When I call tokenResponse.parseIdToken(); it is failing with a NullPointerException.
servlet code (authCode is the serverAuthCode I send from the play-games-plugin inside Unity to my GAE):
// (Receive authCode via HTTPS POST)
// Set path to the Web application client_secret_*.json file you downloaded from the
// Google Developers Console: https://console.developers.google.com/apis/credentials?project=_
// You can also find your Web application client ID and client secret from the
// console and specify them directly when you create the GoogleAuthorizationCodeTokenRequest
// object.
String CLIENT_SECRET_FILE = "/mypath/client_secret.json";
// Exchange auth code for access token
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(
JacksonFactory.getDefaultInstance(), new FileReader(CLIENT_SECRET_FILE));
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
clientSecrets.getDetails().getTokenUri(),
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
authCode,
REDIRECT_URI) // Specify the same redirect URI that you use with your web
// app. If you don't have a web version of your app, you can
// specify an empty string.
.execute();
String accessToken = tokenResponse.getAccessToken();
// Get profile info from ID token -> HERE IT THROWS AN EXCEPTION.
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String userId = payload.getSubject(); // Use this value as a key to identify a user.
String email = payload.getEmail();
boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
String name = (String) payload.get("name");
String pictureUrl = (String) payload.get("picture");
String locale = (String) payload.get("locale");
String familyName = (String) payload.get("family_name");
String givenName = (String) payload.get("given_name");
the token response looks like (its invalid now):
{
"access_token" : "ya29.CjA8A7O96w-vX4OCSPm-GMEPGVIEuRTeOxKy_75z6fbYVSXsdi9Ot3NmxlE-j_t-BI",
"expires_in" : 3596,
"token_type" : "Bearer"
}
In my PHP GAE I always had a idToken inside this constuct which contained my encrypted data. But it is missing now?! So I asssume I do somthing differently in Java or I made a mistake creating the new OAuth 2.0 Client on the google console.
I checked the accessToken manually via:
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=ya29.CjA8A7O96w-vX4OCSPm-GMEPGVIEu-RTeOxKy_75z6fbYVSXsdi9Ot3NmxlE-j_t-BI
{
"issued_to": "48168146---------.apps.googleusercontent.com",
"audience": "48168146---------.apps.googleusercontent.com",
"scope": "https://www.googleapis.com/auth/games_lite",
"expires_in": 879,
"access_type": "offline"
}
Is there something I do not see? Help is very much appreciated...
I found a root cause discussion inside the unity plugin "play-games-services" on github:
https://github.com/playgameservices/play-games-plugin-for-unity/issues/1293
and
https://github.com/playgameservices/play-games-plugin-for-unity/issues/1309
It seems that google switching their authentication flow. In the given links they are talking about adding the email scope inside the plugin to get the idtoken again. I'll try that in the next days and share my experience.
Here is a good explaination about what happens:
http://android-developers.blogspot.de/2016/01/play-games-permissions-are-changing-in.html
If you do what paulsalameh said here (Link to Github) it will work again:
paulsalameh: Sure. After you import the unitypackage, download NativeClient.cs and
PlayGamesClientConfig.cs from my commits (#1295 & #1296), and replace
them in the correct locations.
Afte that "unity play-services-plugin" code update you will be able to add AddOauthScope("email") to PlayGamesClientConfiguration, which allows your server to get the idtoken with the serverAuthCode again...
Code snippet from Unity:
PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder()
.AddOauthScope("email")
.AddOauthScope("profile")
.Build();
Now I am back in business:
{
"access_token" : "ya29.Ci8..7kBR-eBdPw1-P7Pe8QUC7e_Zv7qxCHA",
"expires_in" : 3600,
"id_token" : "eyJhbGciOi......I1NiE0v6kqw",
"refresh_token" : "1/HlSZOo......dQV1y4E",
"token_type" : "Bearer"
}

Accessing authenticated Google Cloud Endpoints API from Google Apps Script

I'm trying to pull some data into a Google sheets spreadsheet from an API that's been built using Google Cloud Endpoints. Here is the API declaration:
#Api(
name = "myendpoint",
namespace =
#ApiNamespace
(
ownerDomain = "mydomain.com",
ownerName = "mydomain.com",
packagePath = "myapp.model"
),
scopes = {SCOPES},
clientIds = {ANDROID_CLIENT_ID, WEB_CLIENT_ID, API_EXPLORER_CLIENT_ID},
audiences = {WEB CLIENT_ID}
)
The method I'm trying to access has authentication enabled by means of the user parameter in the API declaration:
#ApiMethod(name = "ping", httpMethod = HttpMethod.GET, path = "ping")
public StringResponse getPing(User user) throws OAuthRequestException {
CheckPermissions(user);//throws an exception if the user is null or doesn't have the correct permissions
return new StringResponse("pong");
}
This works fine when using the generated client libraries or the gapi js library. However AFAIK I can't use those js libraries in Apps Script.
I've got an OAuth2 flow working using the apps-script-oauth2 library from here, and I'm pretty much using the default setup for creating the service
function getService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth2.createService(SERVICE_NAME)
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('ruggedAuthCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
// Set the scopes to request (space-separated for Google services).
.setScope(SCOPES)
// Below are Google-specific OAuth2 parameters.
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
.setParam('login_hint', Session.getActiveUser().getEmail())
// Requests offline access.
.setParam('access_type', 'offline')
// Forces the approval prompt every time. This is useful for testing,
// but not desirable in a production application.
.setParam('approval_prompt', 'auto')
//.setParam('include_granted_scopes', 'true');
}
These are my methods for accessing the APIs
function getDriveDocs() {
return executeApiMethod('https://www.googleapis.com/drive/v2/','files?maxResults=10');
}
function pingServer(){
return executeApiMethod('https://myapp.appspot.com/_ah/api/myendpoint/v1/','ping');
}
function executeApiMethod(apiUrl, method)
{
//var url = ;
var url = apiUrl + method;
var service = getRuggedService();
return UrlFetchApp.fetch(url, {
'muteHttpExceptions': true,
'method': 'get',
'headers': {
Authorization: 'Bearer ' + service.getAccessToken()
}
});
}
The getDriveDocs() method works perfectly, so I know my auth flow is working correctly. Also, if I call an unauthenticated method in my API I get the correct response. However, when I call the authenticated 'ping' method, the 'user' parameter is always null. Am I missing something in the fetch call? Everything I've read so far seems to suggest that setting
Authorization: 'Bearer ' + service.getAccessToken()
should be enough.
Any help would be much appreciated!
This turned out to be a simple mistake - I had created a new oauth2 credential in the google dev console and had not added the new client id to the API declaration. Here is the working API declaration:
#Api(
name = "myendpoint",
namespace =
#ApiNamespace
(
ownerDomain = "mydomain.com",
ownerName = "mydomain.com",
packagePath = "myapp.model"
),
scopes = {SCOPES},
clientIds = {ANDROID_CLIENT_ID, WEB_CLIENT_ID, API_EXPLORER_CLIENT_ID, GAPPS_CLIENT_ID},
audiences = {WEB CLIENT_ID}
)

How do I use the Google API Explorer to test my own App Engine Endpoints using OAuth?

I have an Endpoints API deployed on App Engine. I have no problem using the Google API Explorer to make requests to API methods that do NOT require being logged in. The URL I'm using for that is:
https://developers.google.com/apis-explorer/?base=https://[MY_APP_ID].appspot.com/_ah/api
Where I am stuck is calling API methods that require the user to be logged in, such as this one:
#ApiMethod(name = "config.get",
clientIds = {"[MY_CLIENT_ID].apps.googleusercontent.com", "com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID"},
audiences = {"[MY_APP_ID].appspot.com"},
scopes = {"https://www.googleapis.com/auth/userinfo.email"})
public Config getConfig(User user) throws OAuthRequestException {
log.fine("user: " + user);
if (user == null) {
throw new OAuthRequestException("You must be logged in in order to get config.");
}
if (!userService.isUserAdmin()) {
throw new OAuthRequestException("You must be an App Engine admin in order to get config.");
}
...
On the API Explorer there's a switch top right that, when clicked, allows me to specify scopes and authorise. I'm doing that with just the userinfo.email scope checked. It makes no difference. The response I get from my call is:
503 Service Unavailable
- Show headers -
{
"error": {
"errors": [
{
"domain": "global",
"reason": "backendError",
"message": "java.lang.IllegalStateException: The current user is not logged in."
}
],
"code": 503,
"message": "java.lang.IllegalStateException: The current user is not logged in."
}
}
Back when Endpoints was in Trusted Tester phase, I remember there being a manual step in the OAuth2 Playground to get an ID token instead of an access token or some such thing. If that is still required, any mention of that seems to have disappeared from the Endpoints docs now and I see now way to swap out tokens in the API Explorer either.
I see you've got "com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID" in quotes. If that's not a typo in your transcription to Stack Overflow, that's a problem. The value is already a string, so you're just passing in the text com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID (not the actual client ID) as the whitelisted scope. That won't work. Try this instead:
#ApiMethod(name = "config.get",
clientIds = {"[MY_CLIENT_ID].apps.googleusercontent.com", com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID},
audiences = {"[MY_APP_ID].appspot.com"},
scopes = {"https://www.googleapis.com/auth/userinfo.email"})
Edit: isUserAdmin is unsupported within Endpoints, and is likely a secondary cause of error. I'd suggest filing a feature request for supporting this method on the provided User object (we likely won't provide support for the user service itself, so it's separate from OAuth login.)
I don't know when this was introduced, but if you use OAuth2, instead of UserService.isUserAdmin() you can use OAuthServiceFactory.getOAuthService().isUserAdmin(EMAIL_SCOPE) where EMAIL_SCOPE is "https://www.googleapis.com/auth/userinfo.email".
This makes it easy to use the old OpenId or OAUth2:
boolean isAdmin = false;
try {
isAdmin = userService.isUserAdmin());
} catch (IllegalStateException e1) {
try {
isAdmin = OAuthServiceFactory.getOAuthService().isUserAdmin(EMAIL_SCOPE);
} catch (Exception e2) {}
}
The original question was asked several years ago, but maybe this will help others.

How to get Google profile info including custom fields from an Apps Domain user?

Using the user.profile and user.email scope and the /oauth2/v2/userinfo feed doesn't seem to return any custom fields (in my case Department) or phone numbers. These fields show up in the Domain Shared Contacts directory.
Is there perhaps an Apps Domain specific feed URL something like /oauth2/{DOMAIN}/v2/userinfo ?
Does the API/Service not support any custom fields yet?
Is there a way to fudge this into working?
Read access to your own Apps Domain Shared Contacts profile that's connected to your account shouldn't be so difficult.
I'd prefer a non-admin solution because my domain uses Common Access Cards w/ SAML authentication so I can't just store admin credentials (user : password) in an App Engine app and access the /m8/ feed. If there's a flow to access Domain Shared Contacts (with custom fields) with a beforehand authorized consumer key and secret I'd be interested in the instructions for getting that to work.
EDIT Jay Lee nailed it "https://www.google.com/m8/feeds/gal/{domain}/full"
Here's the proof of concept script using Google Apps Script (I'll add the final OAuth2 version when I finish it)
function getGal(email, passwd, domain) {
var res = UrlFetchApp.fetch("https://www.google.com/accounts/ClientLogin", {
contentType: "application/x-www-form-urlencoded",
method: "post",
payload: { "Email": email, "Passwd": passwd, "accountType": "HOSTED", "service":"cp" }
});
var auth = res.getContentText().match(/Auth=(.*)/i)[1];
Logger.log("Auth: " + auth);
res = UrlFetchApp.fetch("https://www.google.com/m8/feeds/gal/" + domain + "/full", {
method: "get",
headers: { "Authorization": "GoogleLogin auth=" + auth, "GData-Version": "1.0" }
});
Logger.log(res.getHeaders());
Logger.log(res.getContentText());
}
EDIT 2 OAuth version that returns JSON and only the info for the user accessing the script.
function googleOAuthM8() {
var oAuthConfig = UrlFetchApp.addOAuthService("m8");
oAuthConfig.setRequestTokenUrl('https://www.google.com/accounts/OAuthGetRequestToken?scope=https://www.google.com/m8/feeds/');
oAuthConfig.setAuthorizationUrl('https://www.google.com/accounts/OAuthAuthorizeToken');
oAuthConfig.setAccessTokenUrl('https://www.google.com/accounts/OAuthGetAccessToken');
oAuthConfig.setConsumerKey('anonymous');
oAuthConfig.setConsumerSecret('anonymous');
return {oAuthServiceName:"m8", oAuthUseToken:'always'};
}
function getGal(domain) {
res = UrlFetchApp.fetch("https://www.google.com/m8/feeds/gal/" + domain + "/full?alt=json&q=" + Session.getActiveUser().getEmail(), googleOAuthM8());
Logger.log(res.getHeaders());
Logger.log(res.getContentText());
}
Any non-admin user can access the GAL programmatically, see:
https://github.com/google/gfw-deployments/blob/master/apps/shell/gal/gal_feed.sh
I don't believe this API call is documented or supported officially but it works even with OAuth authentication rather than the example's ClientLogin (tested on the OAuth 2.0 playground with a non-admin user and the standard https://www.google.com/m8/feeds/ Contacts scope).
Note that the Global Address List is a compilation of user profiles, groups and shared contacts. You'll need to parse it out to find the user(s) you wish to get department information for.
I would utilize the Google Apps Profiles API to do this. It'll give you a bunch of meta information, including profile data and even profile photos:
https://developers.google.com/google-apps/profiles/
Even if you're using PIV/CAC/SAML, you will be able to auth using Two-Legged-OAuth.
https://developers.google.com/accounts/docs/OAuth#GoogleAppsOAuth
Two-legged-oauth is the path of least resistance, but you should also take a look at OAuth2, especially the JWT-signed service accounts portion -- however, it can be a little tricky to get working with the older GData xml apis.
As far as fields available go, you'll have to work with the ones on this page. There are extended properties where you add in arbitrary data, but they don't show up in the Contacts browser with Google Mail itself:
https://developers.google.com/gdata/docs/2.0/elements#gdProfileKind
On a sidenote, if you're in an LDAP environment (and since you mentioned CAC, I think you probably are), you should take a look at Google Apps Directory Sync, which can synchronize that profile data with your local AD/LDAP.
source: I deployed Google Apps to large organizations (3000+), public and private.
I have used the following approach with TwoLeggedOAuthHmacToken:
Consumer key and secret can be found in google apps admin dashboard
CONSUMER_KEY = 'domain.com'
CONSUMER_SECRET = 'secret_key'
class ContactClient():
def __init__(self, username):
# Contacts Data API Example ====================================================
self.requestor_id = username + '#' + CONSUMER_KEY
self.two_legged_oauth_token = gdata.gauth.TwoLeggedOAuthHmacToken(
CONSUMER_KEY, CONSUMER_SECRET, self.requestor_id)
self.contacts_client = gdata.contacts.client.ContactsClient(source=SOURCE_APP_NAME)
self.contacts_client.auth_token = self.two_legged_oauth_token
def newuser(self, username):
self.contacts_client.auth_token.requestor_id = username + '#' + CONSUMER_KEY
def getContacts(self, username=None):
if username:
self.newuser(username)
return self.contacts_client.GetContacts()
class MainPage(webapp2.RequestHandler):
def get(self):
contacts = ContactClient(username='username')
feed = contacts.getContacts()
output = ""
if feed:
for entry in feed.entry:
if entry.title and entry.title.text:
output += entry.title.text + "<br/>"
for email in entry.email:
if email.primary and email.primary == 'true':
output += ' %s<br/>' % (email.address)
self.response.headers['Content-Type'] = 'text/html'
self.response.write('''<h1>Contact Access via GData Client</h1>''' + output)

Resources