Calling GAS Script published as a Service from GWT - google-app-engine

I have created a Google Apps Script doPost script that I have published as a Service, only available to myself (as described in https://developers.google.com/apps-script/guide_user_interfaces#RunDecision, section "Publishing a Script as a Service").
I have now a URL like https://sites.google.com/a/macros/[google apps domain]/exec?service=[service key]
I want to call this service from a Google App Engine GWT application, but I don't know how to manage with authentication.
If selecting the "Allow anyone to invoke this service" then "Allow anonymous access", then I can call this service from AppEngine, but in my case, I absolutely need the authentication.
Do you have any idea how to handle it ?

If you only need to call this script from server to server and both of the endpoints are in your ownership, you could use a shared secret to do so, e.g.
Apps Script:
function doPost(e) {
if(e.parameters.secret != 'mysecret') {
return ContentService.createTextOutput("Nice try!");
}
// your code here
}
and transmit it with the request. If you only share your script with "Anyone having the link" that should provide reasonable security - make sure you never log that request nor include it in an error message however ;)

Related

Use path/slug after Web App's base url in Google Apps Script

I'm looking to make the url by adding a path which is something like this below in Google Apps Script:
https://script.google.com/macros/s/APP_ID/exec/fileName.txt
How can I achieve this for Web App service?
I believe your goal as follows.
You want to access to Web Apps using the URL of https://script.google.com/macros/s/APP_ID/exec/fileName.txt.
For this, how about this answer? I think that you can achieve your goal using Web Apps. As a sample case, I would like to explain about this using a sample script for downloading a text file, when an user accesses to https://script.google.com/macros/s/APP_ID/exec/fileName.txt.
Usage:
Please do the following flow.
1. Create new project of Google Apps Script.
Sample script of Web Apps is a Google Apps Script. So please create a project of Google Apps Script.
If you want to directly create it, please access to https://script.new/. In this case, if you are not logged in Google, the log in screen is opened. So please log in to Google. By this, the script editor of Google Apps Script is opened.
2. Prepare script.
Please copy and paste the following script (Google Apps Script) to the script editor. This script is for the Web Apps.
function doGet(e) {
const path = e.pathInfo;
if (path == "filename.txt") {
const sampleTextData = "sample";
return ContentService.createTextOutput(sampleTextData).downloadAsFile(path);
}
return ContentService.createTextOutput("Wrong path.");
}
In order to retrieve the value of fileName.txt in https://script.google.com/macros/s/APP_ID/exec/fileName.txt, please use pathInfo.
For example, when you check e of doGet(e) by accessing with https://script.google.com/macros/s/APP_ID/exec/fileName.txt, you can retrieve {"contextPath":"","contentLength":-1,"parameter":{},"parameters":{},"queryString":"","pathInfo":"fileName.txt"}.
In this case, the GET method is used.
3. Deploy Web Apps.
On the script editor, Open a dialog box by "Publish" -> "Deploy as web app".
Select "Me" for "Execute the app as:".
By this, the script is run as the owner.
Select "Anyone, even anonymous" for "Who has access to the app:".
In this case, no access token is required to be request. I think that I recommend this setting for your goal.
Of course, you can also use the access token. At that time, please set this to "Anyone". And please include the scope of https://www.googleapis.com/auth/drive.readonly and https://www.googleapis.com/auth/drive to the access token. These scopes are required to access to Web Apps.
Click "Deploy" button as new "Project version".
Automatically open a dialog box of "Authorization required".
Click "Review Permissions".
Select own account.
Click "Advanced" at "This app isn't verified".
Click "Go to ### project name ###(unsafe)"
Click "Allow" button.
Click "OK".
Copy the URL of Web Apps. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please redeploy as new version. By this, the modified script is reflected to Web Apps. Please be careful this.
4. Run the function using Web Apps.
Please access to https://script.google.com/macros/s/###/exec/filename.txt using your browser. By this, a text file is downloaded.
Note:
When you modified the script of Web Apps, please redeploy the Web Apps as new version. By this, the latest script is reflected to the Web Apps. Please be careful this.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
Updated on February 14, 2023
In the current stage, it seems that pathInfo can be used with the access token. It supposes that the following sample script is used.
function doGet(e) {
return ContentService.createTextOutput(JSON.stringify(e));
}
When you log in to your Google account and you access https://script.google.com/macros/s/###/exec/sample.txt with your browser, {"contextPath":"","parameter":{},"pathInfo":"sample.txt","contentLength":-1,"parameters":{},"queryString":""} can be seen.
In this case, when you access it without logging in Google account, even when Web Apps is deployed as Execute as: Me and Who has access to the app: Anyone, the log in screen is opened. Please be careful about this.
And, if you want to access with https://script.google.com/macros/s/###/exec/sample.txt using a script, please request it by including the access token. The sample curl command is as follows. In this case, the access token can be used as the query parameter. Please include one of the scopes of Drive API in the access token.
curl -L "https://script.google.com/macros/s/###/exec/sample.txt?access_token=###"
By this, the following result is returned.
{"contextPath":"","queryString":"access_token=###"},"pathInfo":"sample.txt","parameters":{"access_token":["###"]},"contentLength":-1}

AppEngine authentication through Node.js

I'm trying to write a VSCode extension where users could log into Google AppEngine with a google account, and I need to get their SACSID cookie to make appengine requests.
So I'm opening a browser window at
https://accounts.google.com/ServiceLogin?service=ah&passive=true&continue=https://appengine.google.com/_ah/conflogin%3Fcontinue%3Dhttp://localhost:3000/
(generated by google.appengine.api.users.create_login_url)
The user logs in and is redirected to my local webserver at
localhost:3000/_ah/conflogin/?state={state}
Now I try to forward the request to my AppEngine app (since it knows how to decode the state parameter), so I do a request to
https://my-app.appspot.com/_ah/conflogin/?state={state}
basically just replacing localhost with the actual app.
but it doesn't work, presumably because the domain is different. I assume this is on purpose, for security.
Is there any way I can make this work ?
Not ideal, but the only solution I've found is to have an endpoint on my GAE instance that does the redirection. Then I can set that as the continue url, when I'm starting the authentication process
https://accounts.google.com/ServiceLogin?service=ah&passive=true&continue=https://appengine.google.com/_ah/conflogin%3Fcontinue%3Dhttps://my-app.appspot.com/redirect?to=http://localhost:3000
I think you should center the attention on the protocols you are using, since it’s known that the cookie name is based on the http protocol (HTTP : ACSID, HTTPS:SACSID), and that’s the security perspective till this point for me.
Having the error you are facing now would be helpful to understand the problem better. Also, how are you performing the call to the API and the code you are using would be helpful too.

How to configure external authentication schemes after service startup

I want to roll my own instance of identityserver4 as authentication instance for a service I am hosting in the cloud.
My concrete scenario is that I want to be able for customers to set up using their ADFS in a self service manner similar to how slack, expensify or namely allow this.
I know how federating works. Usually in the Startup in ConfigureServices you add the following
services.AddAuthentication()
.AddCookie("cookie")
.AddSaml2p("idp1", options => {
.....
.AddSaml2p("idp3", options => {
and so on and I can use the same of the scheme to later challenge the external ADFS. But this is only and always happening at the startup of the service. When a customer is configuring the connection to his ADFS later my service is already up and running and I don't want to restart my service to be able to configure the federation between my identityserver and the external provider.
Is there a way to configure this without restarting the service?
There's 2 options as I see it:
https://www.identityserver.com/documentation/dynamic-authentication-providers
OR - as I had to a couple of years ago before anything like the above existed - creating your own version of the authentication handler (surprisingly few changes required) to accept parameters via the properties passed to ChallengeAsync(). In the case of OIDC all I needed to override was the authority URL and client ID since it's I'm only using id_token.

Configure Tomcat for Kerberos and Impersonation

I would like to configure Tomcat to be able to connect to AD and authenticate users accordingly.
In addition, I would also like to invoke some web services (in this case, Share Point) using the client credentials.
So far, I've managed to successfully configure Tomcat to use SPNEGO authentication, as described in the tutorial at http://tomcat.apache.org/tomcat-7.0-doc/windows-auth-howto.html. Note that I have used Tomcat's SPNEGO authentication (not Source Forge's or Waffle).
I did not use Source Forge's implementation since I wanted to keep things simple and use Tomcat's as provided out of the box. In addition, I wanted all the authentication and authorization to be handled by Tomcat, using the SPNEGO as the authentication method in WEB.XML and Tomcat's JNDI realm for authorization.
Also I have not used WAFFLE, since this is Windows only.
I'm using CXF as my Web Service stack. According to the CXF documentation at http://cxf.apache.org/docs/client-http-transport-including-ssl-support.html#ClientHTTPTransport%28includingSSLsupport%29-SpnegoAuthentication%28Kerberos%29, all you need to do to authenticate with the a web service (in my case, Share Point) is to use:
<conduit name="{http://example.com/}HelloWorldServicePort.http-conduit"
xmlns="http://cxf.apache.org/transports/http/configuration">
<authorization>
<AuthorizationType>Negotiate</AuthorizationType>
<Authorization>CXFClient</Authorization>
</authorization>
</conduit>
and configure CXFClient in jaas.conf (in my case, where Tomcat's server JAAS configuration is located, such that my jass.conf looks like:
CXFClient {
com.sun.security.auth.module.Krb5LoginModule required client=true useTicketCache=true debug=true;
};
com.sun.security.jgss.krb5.initiate {
com.sun.security.auth.module.Krb5LoginModule required
doNotPrompt=true
principal="HTTP/tomcatsrv.corporate.intra#CORPORATE.INTRA"
useKeyTab=true
keyTab="C:/Program Files/Apache/apache-tomcat-7.0.27/conf/tomcatsrv.keytab"
storeKey=true
debug=true;
};
com.sun.security.jgss.krb5.accept {
com.sun.security.auth.module.Krb5LoginModule required
doNotPrompt=true
principal="HTTP/tomcatsrv.corporate.intra#CORPORATE.INTRA"
useKeyTab=true
keyTab="C:/Program Files/Apache/apache-tomcat-7.0.27/conf/tomcatsrv.keytab"
storeKey=true
debug=true;
};
Yet, when I'm invoking the web service, it is invoked under the service username (i.e. Tomcat's username configured in AD and in tomcatsrv.keytab), rather than the client's username (e.g. duncan.attard).
So my question is this: Is there some way in which the client's username can be delegated (or use some sort of impersonation) to CXF so that when I invoke Share Point's web service (e.g. I want to upload a file using Copy.asmx), the file is uploaded as duncan.attard and not as tomcat.srv.
Thanks all, your help is much appreciated.
Technically, this works perfectly. Here's the recipe:
You do not need a login module name if you work with credential delegation.
You have to make sure that the user account is eligible for delegation.
Take a look at the implementation of Tomcat's GenericPrincipal, it will save you the GSS credential if there is one. Cast request.getPrincipal to GenericPrincipal and get the credential.
Now say you have the credential:
Construct a Subject with the Principal and the GSSCredential as private credential.
Wrap the CXF code into a PrivilegedAction.
Pass the constructed subject and an instance of your privileged action to the Subject.doAs method and the system will construct an AccessControlContext on behalf of the passed subject and will invoke everything in JAAS on behalf of that context. CXF should use those if it is implemented correctly. This is like su or sudo on Unix.
The easiest way to test that is to create an InitialDirContext in the privileged action on behalf of the client to your Active Directory. This is how I test a working credential delegation environment.

302 status when copying data to another app in AppEngine

I'm trying to use the "Copy to another app" feature of AppEngine and keep getting an error:
Fetch to http://datastore-admin.moo.appspot.com/_ah/remote_api failed with status 302
This is for a Java app but I followed the instructions on setting up a default Python runtime.
I'm 95% sure it's an authentication issue and the call to remote_api is redirecting to the Google login page. Both apps use Google Apps as the authentication mechanism. I've also tried copying to and from a third app we have which uses Google Accounts for authentication.
Notes:
The user account I log in with is an Owner on all three apps. It's a Google Apps account (if that wasn't obvious).
I have a gmail account this is an Owner on all three apps as well. When I log in to the admin console with it, I don't see the datastore admin console at all when I click it.
I'm able to use the remote_api just fine from the command-line after I enter my details
Tried with both the Python remote_api built-in and the Java one.
I've found similar questions/blog posts about this, one of which required logging in from a browser, then manually submitting the ACSID cookie you get after that's done. Can't do that here, obviously.
OK, I think I got this working.
I'll refer to the two appIDs as "source" and "dest".
To enable datastore admin (as you know) you need to upload a Python project with the app.yaml and appengine_config.py files as described in the docs.
Either I misread the docs or there is an error. The "appID" inthe .yaml should be the app ID you are uploading to to enable DS admin.
The other appID in the appengine_config file, specifically this line:
remoteapi_CUSTOM_ENVIRONMENT_AUTHENTICATION = (
'HTTP_X_APPENGINE_INBOUND_APPID', ['appID'])
Should be the appID of the "source", ID the app id of where the data is coming from in the DS copy operation.
I think this line is what allows the source appID to be authenticated as having permissions to write to the "dest" app ID.
So, I changed that .py, uploaded again to my "dest" app ID. To be sure I made this dummy python app as default and left it as that.
Then on the source app ID I tried the DS copy again, and all the copy jobs were kicked off OK - so it seems to have fixed it.

Resources