OAuth for Blogger - java

I have a desktop Java which access to my Blogger account. To authenticate with the Google Services, I just use the login and password system, which it is now disabled.
This is a short example on how I do that:
public void subePagina(String pagina) throws MalformedURLException, IOException, ServiceException {
myEntry = new Entry();
myEntry.setTitle(new PlainTextConstruct(title_page));
myEntry.setContent(new HtmlTextConstruct(page));
myEntry.setDraft(false);
activaServicio();
URL editUrl = new URL("url_feed_entry);
myService.update(editUrl, myEntry);
}
public void activaServicio() throws AuthenticationException {
myService = new GoogleService("blogger","blogname");
myService.setUserCredentials(user, password);
}
I've tried to find a solution but I couldn't see enough information to do that, so, my question is, how can I do the same but ysing OAuth? I need to change more code than the activaServicio Method() ?
Thank you.

Related

Java Opensaml 3.4.6 : authnrequest subject is null - impossible to get user name

Developing a Java EE/JSF application, I am trying to include SAML sso functionality into it. Due to technical requirements (SAP BOBJ SDK) I need to use java 8, so I must stick with opensaml 3.x branch. As the application is some years old, I cannot add spring/spring-security to it just for SAML, that's why my code focuses on raw opensaml usage.
Mimicking the example code of this repository, I implemented the authentication basics:
This first code is called when I reach the "login" page. And send the AuthnRequest to my IDP
#Log4j2
#Named
public class SAMLAuthForWPBean implements Serializable {
private static final BasicParserPool PARSER_POOL = new BasicParserPool();
static {
PARSER_POOL.setMaxPoolSize(100);
PARSER_POOL.setCoalescing(true);
PARSER_POOL.setIgnoreComments(true);
PARSER_POOL.setIgnoreElementContentWhitespace(true);
PARSER_POOL.setNamespaceAware(true);
PARSER_POOL.setExpandEntityReferences(false);
PARSER_POOL.setXincludeAware(false);
final Map<String, Boolean> features = new HashMap<>();
features.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE);
features.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE);
features.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE);
features.put("http://apache.org/xml/features/validation/schema/normalized-value", Boolean.FALSE);
features.put("http://javax.xml.XMLConstants/feature/secure-processing", Boolean.TRUE);
PARSER_POOL.setBuilderFeatures(features);
PARSER_POOL.setBuilderAttributes(new HashMap<>());
}
private String idpEndpoint = "url de azure por";
private String entityId = "glados";
private boolean isLogged;
#Inject
private LoginBean loginBean;
#Inject
private MainBean mainBean;
#Inject
private TechnicalConfigurationBean technicalConfigurationBean;
#PostConstruct
public void init() {
if (!PARSER_POOL.isInitialized()) {
try {
PARSER_POOL.initialize();
} catch (ComponentInitializationException e) {
LOGGER.error("Could not initialize parser pool", e);
}
}
XMLObjectProviderRegistry registry = new XMLObjectProviderRegistry();
ConfigurationService.register(XMLObjectProviderRegistry.class, registry);
registry.setParserPool(PARSER_POOL);
// forge auth endpoint
}
public boolean needLogon() {
return isLogged;
}
public void createRedirection(HttpServletRequest request, HttpServletResponse response)
throws MessageEncodingException,
ComponentInitializationException, ResolverException {
// see this link to build authnrequest with metadata https://blog.samlsecurity.com/2011/01/redirect-with-authnrequest-opensaml2.html
init();
AuthnRequest authnRequest;
authnRequest = OpenSAMLUtils.buildSAMLObject(AuthnRequest.class);
authnRequest.setIssueInstant(DateTime.now());
FilesystemMetadataResolver metadataResolver = new FilesystemMetadataResolver(new File("wp.metadata.xml"));
metadataResolver.setParserPool(PARSER_POOL);
metadataResolver.setRequireValidMetadata(true);
metadataResolver.setId(metadataResolver.getClass().getCanonicalName());
metadataResolver.initialize();
/*
* EntityDescriptor urlDescriptor = metadataResolver.resolveSingle( new CriteriaSet( new BindingCriterion(
* Arrays.asList("urn:oasis:names:tc:SAML:2.0:bindings:metadata"))));
*/
/*entityId = "https://192.168.50.102:8443/360.suite/loginSAML.xhtml";*/
entityId = "glados";
//idp endpoint, je pense => à obtenir des metadata
authnRequest.setDestination(idpEndpoint);
authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
// app endpoint
authnRequest.setAssertionConsumerServiceURL("https://192.168.1.14:8443/360.suite/loginSAML.xhtml");
authnRequest.setID(OpenSAMLUtils.generateSecureRandomId());
authnRequest.setIssuer(buildIssuer());
authnRequest.setNameIDPolicy(buildNameIdPolicy());
MessageContext context = new MessageContext();
context.setMessage(authnRequest);
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
endpointContext.setEndpoint(URLToEndpoint("https://192.168.1.14:8443/360.suite/loginSAML.xhtml"));
VelocityEngine velocityEngine = new VelocityEngine();
velocityEngine.setProperty("resource.loader", "classpath");
velocityEngine.setProperty("classpath.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
velocityEngine.init();
HTTPPostEncoder encoder = new HTTPPostEncoder();
encoder.setVelocityEngine(velocityEngine);
encoder.setMessageContext(context);
encoder.setHttpServletResponse(response);
encoder.initialize();
encoder.encode();
}
public String doSAMLLogon(HttpServletRequest request, HttpServletResponse response) {
isLogged = true;
technicalConfigurationBean.init();
return loginBean.generateSSOSession(request, technicalConfigurationBean.getSsoPreferences(),
new SamlSSO(technicalConfigurationBean.getCmsPreferences().getCms()));
}
private NameIDPolicy buildNameIdPolicy() {
NameIDPolicy nameIDPolicy = OpenSAMLUtils.buildSAMLObject(NameIDPolicy.class);
nameIDPolicy.setAllowCreate(true);
nameIDPolicy.setFormat(NameIDType.TRANSIENT);
return nameIDPolicy;
}
private Endpoint URLToEndpoint(String URL) {
SingleSignOnService endpoint = OpenSAMLUtils.buildSAMLObject(SingleSignOnService.class);
endpoint.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
endpoint.setLocation(URL);
return endpoint;
}
private Issuer buildIssuer() {
Issuer issuer = OpenSAMLUtils.buildSAMLObject(Issuer.class);
issuer.setValue(entityId);
return issuer;
}
}
The redirect is successfully processed and the IDP sends back a POST request to my application that call this code :
#Override
public IEnterpriseSession logon(HttpServletRequest request) throws SDKException, Three60Exception {
HTTPPostDecoder decoder = new HTTPPostDecoder();
decoder.setHttpServletRequest(request);
AuthnRequest authnRequest;
try {
decoder.initialize();
decoder.decode();
MessageContext messageContext = decoder.getMessageContext();
authnRequest = (AuthnRequest) messageContext.getMessage();
OpenSAMLUtils.logSAMLObject(authnRequest);
// Here I Need the user
String user = authnRequest.getSubject().getNameID().getValue();
// BOBJ SDK
String secret = TrustedSso.getSecret();
ISessionMgr sm = CrystalEnterprise.getSessionMgr();
final ITrustedPrincipal trustedPrincipal = sm.createTrustedPrincipal(user, cms, secret);
return sm.logon(trustedPrincipal);
} catch (ComponentInitializationException | MessageDecodingException e) {
return null;
}
}
The issue here is that getSubject() is null on this query.
What did I miss here? Do I need to perform other requests? Do I need to add other configuration in my AuthnRequest?
As stated in the comment, I found the reason why my code was not working.
As I also asked this question on a french forum, can can find the translation of this answer here.
Short answer :
Opensaml knows where to send the authn request thanks to the SAMLPeerEntityContext. In my code I put my own application as the target of this request instead of using the idp HTTP-POST bind endpoint. Once this was changed, everything worked, the idp was answering back the SAMLResponse with proper name.
Long version
On my code, I was building the entity context like this :
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
endpointContext.setEndpoint(URLToEndpoint("https://192.168.1.14:8443/360.suite/loginSAML.xhtml"));
This code forces the authn request to be sent to my own application instead of the IDP. As this is the request, it cannot contain the identity.
If I replace this URL by idpEndpoint which I got from the IDP metadata file, the full workflow works as expected.
First something will not work as my IDP forces requests to be signed, so I need to add a signature part.
The "signing and verification" sample of this repository just works for that.
Then, as I need a real identity, I must NOT ask for a transient nameid. In my tests, UNSPECIFIED worked, but PERSISTENT should also make it.
Last, in the ACS receiver, I do NOT receive an authn request but a SAMLResponse with assertions. The code will therefore look like :
String userName =
((ResponseImpl) messageContext.getMessage()).getAssertions().get(0).getSubject().getNameID()
.getValue();
I simplified the code but one, of course, has to check that :
(((ResponseImpl)messageContext.getMessage()).getStatus() is SUCCESS
signatures are valid
assertions are properly populated
Thanks #identigral for your answer in the comment

Gmail Oauth2 Access with intranet web server

Does anyone know the correct setup for the cloud project and redirect URL within the application for the following case?
Setup:
Spring + Apache Wicket
The application is installed on a server (Windows, Linux*) and accessed on the intranet via browser.
*) with or without desktop
Requirements:
Access to one or more Gmail-Accounts to retrieve emails, mark emails as read and move emails to trash
Credentials are stored for each account separately on the server
Creation of the access is done on a client by an admin user in the browser
Consent for an account is done only once on creation, emails are retrieved in a background thread (no user interaction, token is refreshed automatically)
No additional setups on the clients (e.g. changing the host-file, running a background-process/listener); Client could also be a mobile device accessing the intranet
Scopes:
Non-Restricted: userinfo.email
Restricted: gmail.modify
Cloud projects setups/attempts:
Cloud project: Desktop-App; Application: AuthorizationCodeInstalledApp.authorize - Does not work - the consent screen is opened on the server if this is used
Cloud project: Desktop-App; Application: urn:ietf:wg:oauth:2.0:oob as redirect url and popup on the client - Worked but Google is discontinuing oob
Current: Cloud project: Web-App with a public redirect url; Application: redirected to our website - only to show the auth code, which can be pasted in the application open in the browser
public String getAuthorizationUrl(String clientId, String clientSecret, String credentialPath)
{
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
final List<String> SCOPES =
Arrays.asList(new String[] {GmailScopes.GMAIL_MODIFY, Oauth2Scopes.USERINFO_EMAIL});
Details details = new Details();
details.setClientId(clientId);
details.setClientSecret(clientSecret);
GoogleClientSecrets clientSecrets = new GoogleClientSecrets();
clientSecrets.setInstalled(details);
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new File(credentialPath)))
.setApprovalPrompt("force")
.setAccessType("offline")
.build();
/* approval prompt and access type were not needed for desktop-app;
* refresh token was generated anyway, they had to be added for web-app
* to get a refresh token */
String redirUri = "https://example.com/redirect";
AuthorizationCodeRequestUrl authorizationUrl =
flow.newAuthorizationUrl().setRedirectUri(redirUri);
return authorizationUrl.build();
}
Google Oauth verification:
Google says that according to the generated traffic, the app is running on a web server and we need to change it to a local URL, otherwise we need a security assessment because the data is stored on a web server.
While it's technically true that it's running on a web server, it's an intranet server.
It's not possible to define a fixed local URL since the servers IP could be different for each user that is installing the app on his server.
You have several issues here. The first is that you are using a desktop application to run a web app. GoogleAuthorizationCodeFlow.Builder is designed for use with installed apps desktop apps or console applications. Its not designed to be run hosted on a web server.
Follow the following example Web server applications
public class CalendarServletSample extends AbstractAuthorizationCodeServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// do stuff
}
#Override
protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
GenericUrl url = new GenericUrl(req.getRequestURL().toString());
url.setRawPath("/oauth2callback");
return url.build();
}
#Override
protected AuthorizationCodeFlow initializeFlow() throws IOException {
return new GoogleAuthorizationCodeFlow.Builder(
new NetHttpTransport(), GsonFactory.getDefaultInstance(),
"[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
DATA_STORE_FACTORY).setAccessType("offline").build();
}
#Override
protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
// return user ID
}
}
public class CalendarServletCallbackSample extends AbstractAuthorizationCodeCallbackServlet {
#Override
protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
throws ServletException, IOException {
resp.sendRedirect("/");
}
#Override
protected void onError(
HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
throws ServletException, IOException {
// handle error
}
#Override
protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
GenericUrl url = new GenericUrl(req.getRequestURL().toString());
url.setRawPath("/oauth2callback");
return url.build();
}
#Override
protected AuthorizationCodeFlow initializeFlow() throws IOException {
return new GoogleAuthorizationCodeFlow.Builder(
new NetHttpTransport(), GsonFactory.getDefaultInstance()
"[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
DATA_STORE_FACTORY).setAccessType("offline").build();
}
#Override
protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
// return user ID
}
}
installing app.
You have stated this
It's not possible to define a fixed local URL since the servers IP could be different for each user that is installing the app on his server.
Which implies to me that you are giving the code for this app directly to your users with out it being compiled. This includes your credeitnals.json file. YOu may not do this this is against the TOS. Can I really not ship open source with Client ID?
Asking developers to make reasonable efforts to keep their private keys private and not embed them in open source projects.
You should be instructing your users in how to create their own client id and client secrete. in order to get their own creditnals.json file.
They can then supply their own ip address of their server.
In which case your issue with verification is no longer an issue. You dont need to verfy for them. They should be doing that themselves.
push back on internal app
When your users go to verification their app make sure that they are clear with Google that this is an internal app. Hosted on their intranet. They should not need verification.

Unable to authenticate with Google Tasks - Homework

This week I had a small program to develop, I needed to create a Web Application(using a Java Servlet on localhost), this Web App is required to do the following:
Obtain and show issues from public organizations from GitHub
Obtain authentication thru OpenID Connect(OAuth 2.0)
Create a Google Task on the default tasklist from an issue using REST
Note: I can only use HTTP, no jar libs
The first part was easy, just had to make the request to the GitHub API and parse the JSON, no problem here
The second part was somewhat easy, I had to create a new Client ID in Google Developer Console, where I'd set the callback and receive the code on it, I'll put it here just in case I'm doing something wrong with it:
Login.java
...
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
System.out.println("--New login request was received --");
resp.setStatus(302);
resp.setHeader("Location", GoogleStuff.AUTH_LINK);
}
...
callback.java
...
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Cookie c = new Cookie("googleCode", req.getParameter("code")); c.setMaxAge(60*60); //1 Hour
//Example code received from Google
//4/6POIUYwZA3tFCnX_2feRDGiPMQOU7At8HyfOzemMkOY.wtiPpsElo8wZoiIBeR5Q2m9sqEaFkwI
resp.addCookie(c);
resp.setStatus(302);
resp.setHeader("Location","searchOrg");
}
...
My problem comes on the third part, I get the response code 401(Not Authorized) from Google, I'm sure I'm doing something wrong, but I don't really know what is wrong. This is probably all wrong, so bear with it :p
Note: To get the API Key I used the Google Developer Console and created a key for Browsers
GoogleStuff.java
...
public static String AUTH_LINK = "https://accounts.google.com/o/oauth2/auth?"+
"scope=https://www.googleapis.com/auth/tasks&"+
"redirect_uri=http://localhost:5005/callback&"+
"response_type=code&" +
"client_id=" + FirstHttpServer.CLIENT_ID +
"&approval_prompt=force";
...
public static void addTask(Issue i, String googleCode){
try {
String postURL = "https://www.googleapis.com/tasks/v1/lists/%40default/tasks?key=" + MyServer.API_KEY;
URL url = new URL(postURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Authorization", googleCode);
BufferedWriter httpRequestBodyWriter = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
httpRequestBodyWriter.write(i.toJson());
httpRequestBodyWriter.close();
Scanner httpResponseScanner = new Scanner(connection.getInputStream());
while(httpResponseScanner.hasNextLine())
System.out.println(httpResponseScanner.nextLine());
httpResponseScanner.close();
} catch (IOException e) {
System.out.println(e.toString());
throw new RuntimeException();
}
I've been at it for a couple days, but with other projects also tightening my time, its becoming increasingly hard for me to find the problem with this, which is why I request your help :)
Thanks in advance
An API will indicate that an access token has expired when it returns a 401 status code.To obtain a new access token, make a request to the token endpoint and include the client_id, client_secret, refresh_token, and grant_type parameters.
You can find more information in this link.
Hope that helps!

Web app and request authentication

I currently have a working web app, but I need to provide means for friend website to consume my data.
There is currently JSON response in place which retrieves some data from my website to caller. It's without authentication currently and I'd like to implement some kind of per request authentication.
My web app has users which are logged in and there is a authentication in place for that. But
I have 3 requests in total for which callers can get data off of my website, what would be the simplest way to add some kind of authentication just for those 3 requests?
I'm using play framework + java
Imo the best options for this would be in the order of simplicity:
Basic authentication (since it's possible to choose either to auth once and then do session-base user recognition or authorize on every request)
2-way SSL
Combination of both
What toolkit do you use for authentication part?
I personally stuck with play-authenticate. So I might be able to answer you question in regard to this toolkit, please apply it to your particular toolkit as needed.
I will provide Basic authentication example as the easiest one. The benefit is: you could start with it and add on top it later (e.g. add Client certificate authentication via Apache later on).
So, my controller code snippet
#Restrict(value = #Group({"ROLE_WEB_SERVICE1"}), handler = BasicAuthHandler.class)
public static Result ws1() {
return TODO;
}
And the authentification handler itself
public class BasicAuthHandler extends AbstractDeadboltHandler {
public static final String HEADER_PREFIX = "Basic ";
private static final String AUTHORIZATION = "authorization";
private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
#Override
public Result beforeAuthCheck(final Http.Context context) {
return basicAuthenticate(context);
}
private Result basicAuthenticate(Http.Context context) {
if (PlayAuthenticate.isLoggedIn(context.session())) {
// user is logged in
return null;
}
final String authHeader = context.request().getHeader(AUTHORIZATION);
if (authHeader == null || !authHeader.toLowerCase().startsWith(HEADER_PREFIX.toLowerCase())) {
return onAuthFailure(context, "Basic authentication header is missing");
}
final String auth = authHeader.substring(HEADER_PREFIX.length());
final byte[] decodedAuth;
final String[] credentials;
try {
decodedAuth = Base64.base64ToByteArray(auth);
credentials = new String(decodedAuth, "UTF-8").split(":");
} catch (final IOException e) {
Logger.error("basicAuthenticate", e);
return Results.internalServerError();
}
if (credentials.length != 2) {
return onAuthFailure(context, "Could not authenticate with absent password");
}
final String username = credentials[0];
final String password = credentials[1];
final AuthUser authUser = new AuthUser(password, username);
final Enum result = AuthProvider.getProvider().loginUser(authUser);
if ("USER_LOGGED_IN".equals(result.name())) {
PlayAuthenticate.storeUser(context.session(), authUser);
return null;
}
return onAuthFailure(context, "Authenticate failure");
}
#Override
public Subject getSubject(final Http.Context context) {
// your implementation
}
#Override
public Result onAuthFailure(final Http.Context context,
final String content) {
// your error hangling logic
return super.onAuthFailure(context, content);
}
}
Hopefully it fills in some blanks

Proper Form of API request to Blogger using Java/App Engine -error 401

I am running into issue in forming the correct api statement for JAVA in calling the Blogger API.
I have tested my statement via the Google Cloud Console and it works but does not work in my code. I am using Google App Engine and have been authorized to use Blogger. The authorization is also tied to the account running Google App Engine.
Any ideas would be helpfull.. have tried many things over the weekend.
Thanks
Request
GET https://www.googleapis.com/blogger/v3/blogs/7676001971884966148/posts?key= {YOUR_API_KEY}
Authorization: Bearer ya29.1.AADtN_Vd7lKj8Xy3KbZ1veJjjjv712Nc1erLY2dmAK3gorNilVd0652vnqrrovfuLfSKkQ
X-JavaScript-User-Agent: Google APIs Explorer
Response
200 OK
- Show headers -
{
"kind": "blogger#postList",
"nextPageToken": "CgkIChjim-ftqygQhIKb6_zjqMNq",
"items": [
{
etc.....
My Code
public class BloggerHandler
{
public static final Logger log = Logger.getLogger(BloggerHandler.class.getName());
public void testCreds() throws Exception {
try{
ArrayList<String> scopes = new ArrayList<String>();
scopes.add("https://www.googleapis.com/auth/blogger");
scopes.add("https://www.googleapis.com/auth/blogger.readonly");
AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
AppIdentityService.GetAccessTokenResult accessToken = appIdentity.getAccessToken(scopes);
// The token asserts the identity reported by appIdentity.getServiceAccountName()
JSONObject request = new JSONObject();
//request.put("maxPosts", "1");
//request.put("view", "AUTHOR");
log.info("request!!!" + request);
URL url = new URL("https://www.googleapis.com/blogger/v3/blogs/7676001971884966148/posts?");
log.info("URL:" + url);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("GET");
connection.addRequestProperty("Content-Type", "application/json");
connection.addRequestProperty("Authorization", "OAuth" + accessToken.getAccessToken());
log.info("Con!!" + connection);
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
request.write(writer);
writer.close();
log.info("connection:" + connection.getResponseCode());
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
// Note: Should check the content-encoding.
JSONTokener response_tokens = new JSONTokener(connection.getInputStream());
JSONObject response = new JSONObject(response_tokens);
log.info("resp:" + response.get("title"));
} // end if
else {
throw new Exception();
}// end else
} // end try
catch (Exception e) {
// Error handling elided.
log.info("ex:" + e);
}
// end catch
}// end void
}// end class
After a few long nights I was able to figure out how to access the Google Blogger API from a GAE project.
There are a couple of key things that I did that may help aid you.
In your Google Appe Engine project make sure that it is linked to the Google API Console. IN the GAE Admin project screen you should see a Google APIs Console Project Number:XX
In the Google API Cloud console make sure you are authorized for Blogger (or whatever cloud API you want to use). Create a project (website.. etc) and copy down the API string that it gives you.
Once you have that API string the code below should get you started with base connection.
The code below should return a "200" connection with the stats of the current blog you are trying to reach. From here you can expand upon the API.
// On a side note I know there is a way to read the API from Google Cloud so it does not have to be part of the code. Still working on that workflow.
import java.util.logging.Logger;
import java.util.Arrays;
import com.google.api.client.googleapis.extensions.appengine.auth.oauth2.AppIdentityCredential;
import com.google.api.services.blogger.Blogger;
import com.google.api.services.blogger.Blogger.Blogs.GetByUrl;
import com.google.api.services.blogger.Blogger.Posts.List;
import com.google.api.services.blogger.BloggerScopes;
import com.google.api.services.blogger.model.Blog;
import com.google.api.services.blogger.model.Post;
import com.google.api.services.blogger.model.PostList;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.extensions.appengine.http.UrlFetchTransport;
import java.io.IOException;
public class BlogHandler
{
public Blogger blogger = null;
public Blog blog;
public java.util.List<Post> posts;
public static final Logger log = Logger.getLogger(EngineParseFeed.class.getName());
static final String API_KEY = "{Your GOOGLE CLOUD API}";
public BlogHandler() {}
public void setupService () throws IOException {
AppIdentityCredential credential = null;
credential = new AppIdentityCredential(Arrays.asList(BloggerScopes.BLOGGER)); // Add your scopes here
this.blogger = new Blogger.Builder(new UrlFetchTransport(), new JacksonFactory(), credential).setApplicationName("trivalAPPName").build();
}
public void executeGetBlogByUrl (String url) throws IOException {
GetByUrl request = blogger.blogs().getByUrl( url );
this.blog = request.setKey(API_KEY).execute();
log.info ("Blog" + this.blog);
}
I managed to get the Blogger API working properly on Google App Engine by using two servlets and modelling them after the examples on this page: https://developers.google.com/google-apps/tasks/oauth-authorization-callback-handler. The example code is out of date and uses some kind of deprecated draft10 library.
Here's the working version for the servlet that posts to Blogger:
public class BloggerServlet
{
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
public static GoogleAuthorizationCodeFlow flow;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Entity OAuthTokenEntity;
OAuthProperties oauthProperties = new OAuthProperties();
String OAuthAccessToken, OAuthRefreshToken;
try
{
OAuthTokenEntity = datastore.get(KeyFactory.createKey("OAuthTokenEntity","OA"));
OAuthAccessToken = OAuthTokenEntity.getProperty("OAuthAccessToken").toString();
OAuthRefreshToken = OAuthTokenEntity.getProperty("OAuthRefreshToken").toString();
}
catch(EntityNotFoundException e)
{
Collection<String> scopes = Arrays.asList(BloggerScopes.BLOGGER);
flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
CLIENT_ID, CLIENT_SECRET, scopes)
.setAccessType("offline")
.setApprovalPrompt("auto").build();
String url = flow.newAuthorizationUrl()
.setRedirectUri(OAuthCodeCallbackHandlerServlet.getOAuthCodeCallbackHandlerUrl(request))
.build();
response.sendRedirect("http://OAuthCodeCallbackHandlerServlet");
return;
}
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.build();
credential.setAccessToken(OAuthAccessToken);
credential.setRefreshToken(OAuthRefreshToken);
Blogger blog = new Blogger.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("APP_NAME").setHttpRequestInitializer(credential).build();
}
}
And here is the working version of the Servlet that handles the callback:
public class OAuthCodeCallbackHandlerServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String[] code = request.getParameterValues("code");
GoogleTokenResponse tokenResponse = BloggerServlet.flow.newTokenRequest(code[0]).setRedirectUri(getOAuthCodeCallbackHandlerUrl(request)).execute();
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Entity OAuthTokenEntity = new Entity("OAuthTokenEntity","OA");
OAuthTokenEntity.setProperty("OAuthAccessToken", tokenResponse.getAccessToken());
OAuthTokenEntity.setProperty("OAuthRefreshToken",tokenResponse.getRefreshToken());
datastore.put(OAuthTokenEntity);
response.sendRedirect("http://BloggerServlet");
}
public static String getOAuthCodeCallbackHandlerUrl(HttpServletRequest request)
{
StringBuilder oauthURL = new StringBuilder();
oauthURL.append(request.getScheme() + "://");
oauthURL.append(request.getServerName());
oauthURL.append(request.getServerPort() == 80 ? "" : ":" + request.getServerPort());
oauthURL.append(request.getContextPath());
oauthURL.append(URL_MAPPING);
oauthURL.append(request.getPathInfo() == null ? "" : request.getPathInfo());
return oauthURL.toString();
}
}
I have been working on this and my conclusion is:
To make a request for a blog or post, as long as they are public, you don't have to authenticate, i.e. you don't need OAuth for this kind of requests.Still, you have to identify your application and you do that with the APP_KEY. As stated here, not any application can use the Blogger API, it has to be authorized and for this reason is this key needed.
Based on 1., you dont need to create an AppIdentityCredential. I just set the third parameter of Builder to null. I tried using your code but it didn't work.
I still have a question and probably is the same iamkhova has. I think an app can authenticate itself with OAuth using the AppIdentityCredential for GAE apps. But how can you tell google that my application is owner of the blog I am trying to access? I think this is the reason of getting this 401.

Categories