Google OAuth 2.0 include_granted_scopes not working for installed app - bash

I'm attempting to use the new incremental authorization for an installed app in order to add scopes to an existing authorization while keeping the existing scopes. This is done using the new include_granted_scopes=true parameter. However, no matter what I've tried, the re-authorization always overwrites the scopes completely. Here's a minimal Bash PoC script I've written to demo my issue:
client_id='716905662885.apps.googleusercontent.com' # throw away client_id (non-prod)
client_secret='CMVqIy_iQqBEMlzjYffdYM8A' # not really a secret
redirect_uri='urn:ietf:wg:oauth:2.0:oob'
while :
do
echo "Please enter a list of scopes (space separated) or CTRL+C to quit:"
read scope
# Form the request URL
# http://goo.gl/U0uKEb
auth_url="https://accounts.google.com/o/oauth2/auth?scope=$scope&redirect_uri=$redirect_uri&response_type=code&client_id=$client_id&approval_prompt=force&include_granted_scopes=true"
echo "Please go to:"
echo
echo "$auth_url"
echo
echo "after accepting, enter the code you are given:"
read auth_code
# swap authorization code for access token
# http://goo.gl/Mu9E5J
auth_result=$(curl -s https://accounts.google.com/o/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d code=$auth_code \
-d client_id=$client_id \
-d client_secret=$client_secret \
-d redirect_uri=$redirect_uri \
-d grant_type=authorization_code)
access_token=$(echo -e "$auth_result" | \
grep -Po '"access_token" *: *.*?[^\\]",' | \
awk -F'"' '{ print $4 }')
echo
echo "Got an access token of:"
echo $access_token
echo
# Show information about our access token
info_result=$(curl -s --get https://www.googleapis.com/oauth2/v2/tokeninfo \
-H "Content-Type: application/json" \
-d access_token=$access_token)
current_scopes=$(echo -e "$info_result" | \
grep -Po '"scope" *: *.*?[^\\]",' | \
awk -F'"' '{ print $4 }')
echo "Our access token now allows the following scopes:"
echo $current_scopes | tr " " "\n"
echo
echo "Let's add some more!"
echo
done
The script simply performs OAuth authorization and then prints out the scopes the token is currently authorized to use. In theory it should continue to add scopes each time through but in practice, the list of scopes is getting overwritten each time. So the idea would be on the first run, you'd use a minimal scope of something like email and then the next run, tack on something more like read-only calendar https://www.googleapis.com/auth/calendar.readonly. Each time, the user should only be prompted to authorize the currently requested scopes but the resulting token should be good for all scopes including those authorized on previous runs.
I've tried with a fresh client_id/secret and the results are the same. I know I could just include the already authorized scopes again but that prompts the user for all of the scopes, even those already granted and we all know the longer the list of scopes, the less likely the user is to accept.
UPDATE: during further testing, I noticed that the permissions for my app do show the combined scopes of each incremental authorization. I tried waiting 30 seconds or so after the incremental auth, then grabbing a new access token with the refresh token but that access token is still limited to the scopes of the last authorization, not the combined scope list.
UPDATE 2: I've also toyed around with keeping the original refresh token. The refresh token is only getting new access tokens that allow the original scopes, the incrementally added scopes are not included. So it seems effectively that include_granted_scopes=true is having no effect on the tokens, the old and new refresh tokens continue to work but only for their specified scopes. I cannot get a "combined scope" refresh or access token.

Google's OAuth 2.0 service does not support incremental auth for installed/native apps; it only works for the web server case. Their documentation is broken.

Try adding a complete list of scopes to the second request, where you exchange authorization code for an access token. Strangely enough, scope parameter doesn't seem to be documented, but it is present in requests generated by google-api-java-client. For example:
code=foo&grant_type=authorization_code
&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fmyapp%2FoauthCallback
&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.me+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.stream.write
In the web server scenario, a complete list of granted scopes is returned together with authorization code when include_granted_scopes is set to true. This is another bit of information that seems to be missing from linked documentation.
Edit 1 Including a complete list of scopes in the code exchange request works for us in our Java app, but I have just tried your original script with no modification (except for client id/secret) and it works just fine (edited just the ids and tokens):
$ bash tokens.sh
Please enter a list of scopes (space separated) or CTRL+C to quit:
https://www.googleapis.com/auth/userinfo.profile
Please go to:
https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/userinfo.profile&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&client_id=189044568151-4bs2mcotfi2i3k6qp7vq8c6kbmkp2rf8.apps.googleusercontent.com&approval_prompt=force&include_granted_scopes=true
after accepting, enter the code you are given:
4/4qXGQ6Pt5QNYqdEuOudzY5G0ogru.kv_pt5Hlwq8UYKs_1NgQtlUFsAJ_iQI
Got an access token of:
ya29.1.AADtN_XIt8uUZ_zGZEZk7l9KuNQl9omr2FRXYAqf67QF92KqfvXliYQ54ffg_3E
Our access token now allows the following scopes:
https://www.googleapis.com/auth/userinfo.profile
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/plus.me
https://www.googleapis.com/auth/plus.circles.read
You can see that the previously granted scopes are included...

Related

Github API - Get private repositories of user

I have created a script that automatically backs up my GitHub repositories on a hard drive.
I use my Github username in combination with a personal access token to get authorized to Github. Now I've been reading a bit in their documentation about how to get ALL my repositories from the API (public & private), but I can only seem to get the public...
My script: https://github.com/TomTruyen/GitHub-Backup-Script/blob/main/github_backup_script.sh
From what I can understand on line 78, the url should return all my 'owned' repositories (which should include my privates ones)
repositories=$(curl -XGET -s https://"${GITHUB_USERNAME}":"${GITHUB_TOKEN}"#api.github.com/users/"${GITHUB_USERNAME}"/repos?per_page="${repository_count}" | jq -c --raw-output ".[] | {name, ssh_url}")
I have already enabled ALL repository scopes which should give me 'Full control of private repositories (and public)'
I'm out of ideas right now... am I doing something wrong?
NOTE: I'm trying to get my private repositories as a USER, not as an organization
NOTE: ${GITHUB_USERNAME} & ${GITHUB_TOKEN} are variables that I have of course filled in, in my script
You're calling the /users endpoint, but looking at List repositories for the authenticated user it looks like you should be calling /user/repos.
By default this will return all repositories, both public and private, for the currently authenticated user. You'll also need to correctly handle pagination (unless you know for sure you have fewer than 100 repositories).
I was able to fetch a list of all my repositories using the following script:
#!/bin/sh
#
# you must set GH_API_USER and GH_API_TOKEN in your environment
tmpfile=$(mktemp curlXXXXXX)
trap "rm -f $tmpfile" EXIT
page=0
while :; do
let page++
curl -sf -o $tmpfile \
-u "$GH_API_USER:$GH_API_TOKEN" \
"https://api.github.com/user/repos?per_page=100&page=$page&visibility=all"
count=$(jq length $tmpfile)
if [[ $count = 0 ]]; then
break
fi
jq '.[]|.full_name' $tmpfile
done
visibility=all is the key thing here, guys. I spent hours but end up not getting what I wanted. It's been mentioned in the doc as well -
https://docs.github.com/en/rest/repos/repos#list-repositories-for-the-authenticated-user

upload zip file to google drive using curl

I am trying to upload a zip file to Google drive account using curl.
The file is uploaded successfully but the filename is not getting updated. It gets uploaded with default filename i.e. "Untitled".
I am using below command.
curl -k -H "Authorization: Bearer cat /tmp/token.txt" -F "metadata={name : 'backup.zip'} --data-binary "#backup.zip" https://www.googleapis.com/upload/drive/v2/files?uploadType=multipart
You can use Drive API v3 to upload the zip file. The modified curl code is as follows.
curl -X POST -L \
-H "Authorization: Bearer `cat /tmp/token.txt`" \
-F "metadata={name : 'backup.zip'};type=application/json;charset=UTF-8" \
-F "file=#backup.zip;type=application/zip" \
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"
In order to use this, please include https://www.googleapis.com/auth/drive in the scope.
The answer above works fine and was the command I used in uploading my file to Google Drive using Curl. However, I didn't understand what scope was and all of the initial setup required to make this command work. Hence, for documentation purposes. I'll give a second answer.
Valid as at the time of writing...
Visit the Credentials page and create a new credential (this is assuming you have created a project). I created credentials for TVs and Limited devices, so the work flow was similar to:
Create credentials > OAuth client ID > Application Type > TVs and Limited Input devices > Named the client > Clicked Create.
After doing this, I was able to copy the Client ID and Client Secret when viewing the newly created credential.
NB: Only the variables with double asterisk from the Curl commands should be replaced.
Next step was to run the Curl command:
curl -d "client_id=**client_id**&scope=**scope**" https://oauth2.googleapis.com/device/code
Scope in this situation can be considered to be the kind of access you intend to have with the credential having the inputted client_id. More about scope from the docs For the use case in focus, which is to upload files, the scope chosen was https://www.googleapis.com/auth/drive.file.
On running the curl command above, you'll get a response similar to:
{ "device_code": "XXXXXXXXXXXXX", "user_code": "ABCD-EFGH",
"expires_in": 1800, "interval": 5, "verification_url":
"https://www.google.com/device" }
Next step is to visit the verification_url in the response in your browser, provide the user_code and accept requests for permissions. You will be presented with a code when all prompts have been followed, this code wasn't required for the remaining steps (but there may be some reasons to use it for other use cases).
Next step is to use the Curl command:
curl -d client_id=**client_id** -d client_secret=**client_secret** -d device_code=**device_code** -d grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code https://accounts.google.com/o/oauth2/token
You will get a response similar to:
{ "access_token": "XXXXXXXXX", "expires_in": 3599,
"refresh_token": "XXXXXXXXX", "scope":
"https://www.googleapis.com/auth/drive.file", "token_type": "Bearer"
}
Now you can use the access token and follow the accepted answer with a Curl command similar to:
curl -X POST -L \
-H "Authorization: Bearer **access_token**" \
-F "metadata={name : 'backup.zip'};type=application/json;charset=UTF-8" \
-F "file=#backup.zip;type=application/zip" \
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"

Create a curl url in bash

I need to have curl url formatted like this in order for it to be successful.
$instanceUrl/sobjects/$sObject/describe -H 'Authorization: Bearer $apiToken'
This is my script so far.
#!/bin/bash
CURL='/usr/bin/curl'
CURLARGS='-H'
echo "welcome to sf-db-mock"
echo "please enter the instance url of the Salesforce org database you'd like to mock, followed by [ENTER]:"
# get rest endpoint
read instanceUrl
echo "please enter your api token, followed by [ENTER]:"
# get api token from users current session
read apiToken
# get the objects the users wants described
echo "please enter each sObject you'd like to mock by entering its API Name, using a space after each sObject, followed by [ENTER]:"
read -a sObjects
for sObject in ${sObjects[#]}
do
describe="$('sobjects'\/$sObject\/'describe '$CURLARGS ' Authorization: Bearer '$apiToken)"
echo $instanceUrl$describe
done
The error I get while running it is
./mock.txt: line 25: sobjects/survey/describe -H: No such file or directory'
My bash script is executable.
The notation $(a b c) means run command a with arguments b and c.
What you want is to remove the parenthesis:
describe="sobjects/$sObject/describe $CURLARGS 'Authorization: Bearer '$apiToken'"

Authenticate with cas using bash, curl commandline

I found this script for authenticating with cas to obtain a protected URL, from the commandline, using curl and bash(See code below). However, I could not get the script to work. I have verified that I am able to extract the LoginTicket, the JSESSION and provided the right username and password. However, it seems the cas-server does not react to it even though I have verified I provide it with all the right info. It just returns the login-page again and again without any error messages.
Is this script still a viable way of doing this? Or do I need to use the cas REST-API, if I want to get a valid cas ticket using the commandline, now adays?
# Taken from https://gist.github.com/dodok1/4134605
# Usage: cas-get.sh {url} {username} {password} # If you have any errors try removing the redirects to get more information
# The service to be called, and a url-encoded version (the url encoding isn't perfect, if you're encoding complex stuff you may wish to replace with a different method)
DEST="$1"
ENCODED_DEST=`echo "$DEST" | perl -p -e 's/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg' | sed 's/%2E/./g' | sed 's/%0A//g'`
#IP Addresses or hostnames are fine here
CAS_HOSTNAME=team.eea.sk
#Authentication details. This script only supports username/password login, but curl can handle certificate login if required
USERNAME=$2
PASSWORD=$3
#Temporary files used by curl to store cookies and http headers
COOKIE_JAR=.cookieJar
HEADER_DUMP_DEST=.headers
rm $COOKIE_JAR
rm $HEADER_DUMP_DEST
#The script itself is below
#Visit CAS and get a login form. This includes a unique ID for the form, which we will store in CAS_ID and attach to our form submission. jsessionid cookie will be set here
CAS_ID=`curl -s -k -c $COOKIE_JAR https://$CAS_HOSTNAME/cas/login?service=$ENCODED_DEST | grep name=.lt | sed 's/.*value..//' | sed 's/\".*//'`
#Submit the login form, using the cookies saved in the cookie jar and the form submission ID just extracted. We keep the headers from this request as the return value should be a 302 including a "ticket" param which we'll need in the next request
curl -s -k --data "username=$USERNAME&password=$PASSWORD&lt=$CAS_ID&_eventId=submit" -i -b $COOKIE_JAR -c $COOKIE_JAR https://$CAS_HOSTNAME/cas/login?service=$ENCODED_DEST -D $HEADER_DUMP_DEST -o /dev/null
#Linux may not need this line but my response from the previous call has retrieving windows-style linebreaks in OSX
#dos2unix $HEADER_DUMP_DEST > /dev/null
#Visit the URL with the ticket param to finally set the casprivacy and, more importantly, MOD_AUTH_CAS cookie. Now we've got a MOD_AUTH_CAS cookie, anything we do in this session will pass straight through CAS
CURL_DEST=`grep Location $HEADER_DUMP_DEST | sed 's/Location: //'`
curl -s -k -b $COOKIE_JAR -c $COOKIE_JAR $CURL_DEST
#If our destination is not a GET we'll need to do a GET to, say, the user dashboard here
#Visit the place we actually wanted to go to
curl -s -k -b $COOKIE_JAR "$DEST"
You might try extracting the "execution" value like you do the "lt" value and including it in the second curl call.

Google App Scripts curl authorization

Just trying to play with google app scripts. In anonymous mode things seem fine. Except that anyone can call my script simply like that snippet shows:
curl "https://script.google.com/macros/s/.../exec?ip=\"$myIp\""
I used this manual for tips on how to authenticate through GoogleLogin. The problem is "401 Unauthorized" I received when sent auth token and "Me(owner)/Only myself" options were set on google side. (The token seems correct itself. If I omit password or mistype it, then I receive "Bad auth") If I set "Anyone, even anonymous" again, it works, but auth stuff seems like ignored. What's the correct way to do the trick?
#!/bin/bash
gmail=$1
password=$2
myIp=$3
GoogleAuthToken=""
GoogleAuthToken=`curl --silent https://www.google.com/accounts/ClientLogin --data-urlencode Email=$gmail \
--data-urlencode Passwd=$password -d accountType=GOOGLE -d source=YouDontSay -d service=lh2`
echo $GoogleAuthToken
GoogleAuthToken=$(echo "$GoogleAuthToken" | grep 'Auth=' | sed s/Auth=//)
echo $GoogleAuthToken
curl -L --silent --header "Authorization: GoogleLogin auth=$GoogleAuthToken" "https://script.google.com/macros/s/.../exec?ip=\"$myIp\""
You use ClientLogin
https://www.google.com/accounts/ClientLogin
This is google error :
Important: ClientLogin has been officially deprecated since April 20,
2012 and is now no longer available. Requests to ClientLogin will fail
with a HTTP 404 response. We encourage you to migrate to OAuth 2.0 as
soon as possible.

Resources