How do I control Token Response Expiry time for google API access - google-api

I have problem extending the standard one hour for validity of google access token.
One part of my code is getting authorization from the user, using the GoogleAuthorizationCodeFlow as per Google recommendation. This works fine and gives me a TokenResponse that I persist to be used in an other part of the application where the user is not connected.
As per Google documentation, I thought that the "offline" access type in the flow would enable the TokenResponse to be usable as longer as the user doesnt revoke it. But apparently when I use this TokenReponse just after the user authorization, it works fine but when I use it after more than one hour, I get an "invalid credentials" sent back by Google.
Here is the code which creates the TokenResponse once the user has authorized it :
private HttpTransport HTTP_TRANSPORT;
private JacksonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static GoogleAuthorizationCodeFlow flow;
#PostConstruct
public void init() {
try {
HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
} catch (GeneralSecurityException | IOException e) {
logger.info(String.format("Raised Exception while getting GoogleNetHttpTransport : %s", e.getMessage()));
e.printStackTrace();
}
flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, APP_ID, APP_SECRET,
Collections.singleton(CalendarScopes.CALENDAR_READONLY)).setAccessType("offline").build();
}
#RequestMapping(value = Uris.GOOGLERD)
public ModelAndView googleCallBack(HttpServletRequest request, #RequestParam(value = "state", required = false) String state,
#RequestParam(value = "code", required = false) String code,
#RequestParam(value = "error", required = false) String error, Model model) {
DynSubscriber dynSubscriber = (DynSubscriber) request.getSession().getAttribute("dynSubscriber");
ModelAndView toReturn = new ModelAndView("confirmation");
toReturn.addObject("buttonLabel", "Accueil");
try {
AuthorizationCodeTokenRequest tokenRequest = flow.newTokenRequest(code);
TokenResponse tr = tokenRequest.setRedirectUri(request.getRequestURL().toString()).execute();
// Json Conversion of Token Response for future use
StringWriter jsonTrWriter = new StringWriter();
JsonGenerator generator = JSON_FACTORY.createJsonGenerator(jsonTrWriter);
generator.serialize(tr);
generator.flush();
generator.close();
//Persists google access info
dynSubOp.setSPConnexionInfo(dynSubscriber, jsonTrWriter.toString(), DynServiceProviderType.GOOGLECAL);
toReturn.addObject("message","Agenda Google autorisé");
} catch (IOException | DynServicesException e) {
logger.error(String.format("Exception raised in googleCallBack for subscriber %s : %s", dynSubscriber.buildFullName(), e.getMessage()),e);
toReturn.addObject("message", "Problème lors du processus d'autorisation google");
}
return toReturn;
}
}
And here is the offline code which uses this TokenReponse :
private com.google.api.services.calendar.Calendar calendarConnection;
public DynGoogleCalendarRetriever(String subid, String connectionInformation)
throws CalendarConnectionNotAuthorizedException {
TokenResponse tr;
try {
HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
tr = JSON_FACTORY.fromString(connectionInformation, TokenResponse.class);
Credential c = new GoogleCredential().setFromTokenResponse(tr);
calendarConnection = new com.google.api.services.calendar.Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, c)
.build();
} catch (IOException | GeneralSecurityException e) {
logger.error(String.format("Failure creating the credentials for subscriber id %s", subid), e);
throw new CalendarConnectionNotAuthorizedException(String.format(
"Failure creating the credentials for subscriber id %s", subid), e);
}
}

Looks like this was already answered in this other SO question.
To get the refresh token that enables what I want, I need to build the flow with a approval_prompt=force parameter (builder.setApprovalPrompt("force"))
As per the comment, this requires the offline access which is done in the flow initialization.
A complement however : the offline code in my question doesn't work as such although I copied and pasted it from google documentation (probably an older version). The Credential needs to use its Builder object.
Here is the fully functionnal offline code :
TokenResponse tr;
try {
HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
tr = JSON_FACTORY.fromString(connectionInformation, TokenResponse.class);
Credential c = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT).setJsonFactory(JSON_FACTORY)
.setClientSecrets(APP_ID, APP_SECRET).build().setFromTokenResponse(tr);
calendarConnection = new com.google.api.services.calendar.Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, c)
.build();
} catch (IOException | GeneralSecurityException e) {
logger.error(String.format("Failure creating the credentials for subscriber id %s", subid), e);
throw new CalendarConnectionNotAuthorizedException(String.format(
"Failure creating the credentials for subscriber id %s", subid), e);
}

Related

EWS modern authentication using oauth2.0 : The remote server returned an error: (401)Unauthorized

I am trying to do modern authentication for outlook mailbox(which is used to send mail from applications) using microsoft graph access token.
I am succssfully getting the access token from the below code:
public class AuthTokenAccess {
public AuthTokenAccess() {}
public static String getAccessToken(String tenantId, String clientId, String clientSecret, String scope)
{
String endpoint = String.format("https://login.microsoftonline.com/%s/oauth2/token", tenantId);
String postBody = String.format("grant_type=client_credentials&client_id=%s&client_secret=%s&resource=%s&scope=%s",
clientId, clientSecret, "https://management.azure.com/", scope);
String accessToken = null;
try{
HttpURLConnection conn = (HttpURLConnection) new URL(endpoint).openConnection();
conn.setRequestMethod("POST");
conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setDoOutput(true);
conn.getOutputStream().write(postBody.getBytes());
conn.connect();
JsonFactory factory = new JsonFactory();
JsonParser parser = factory.createParser(conn.getInputStream());
//String accessToken = null;
while (parser.nextToken() != JsonToken.END_OBJECT) {
String name = parser.getCurrentName();
if ("access_token".equals(name)) {
parser.nextToken();
accessToken = parser.getText();
}
}
}catch(Exception e) {
}
return accessToken;
}
after getting the access token I am sending this to ExchangeService:
public ExchangeService getExchangeServiceObj(String emailId, String token, String emailServerURI) throws URISyntaxException {
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
if(service != null) {
service.getHttpHeaders().put("Authorization", "Bearer " + token);
service.getHttpHeaders().put("X-AnchorMailbox", emailId);
service.setUrl(new URI(emailServerURI)); //https://outlook.office365.com/EWS/Exchange.asmx
}
LOGGER.debug("getExchangeServiceObj() {}.", "ends");
return service;
}
Here, I am getting the ExchangeService object but when I am trying send mail microsoft.exchange.webservices.data.core.service.item.EmailMessage.sendAndSaveCopy() throws Exception
public void sendMail(String toMail, String ccMail, String subject, String body, String pathOfFileToAttach) {
ExchangeService emailService = getExchangeServiceObj(
ResourceUtils.getPropertyValue("email_user"),
token,
ResourceUtils.getPropertyValue("ews_server"));
if(!StringUtils.hasText(toMail)) {
toMail = ccMail;
}
EmailMessage emessage = new EmailMessage(emailService);
emessage.setSubject(subject);
String strBodyMessage = body;
strBodyMessage = strBodyMessage + "<br /><br />";
LOGGER.info("Body: {} ", body);
MessageBody msg = new MessageBody(BodyType.HTML, strBodyMessage);
emessage.setBody(msg);
emessage.sendAndSaveCopy();
LOGGER.info("Email send {}", "sucessfully");
} catch(Exception e) {
LOGGER.error(Constants.ERROR_STACK_TRACE, e);
throw new CommonException(e);
}
}
Tried with below scopes:
"https://outlook.office.com/EWS.AccessAsUser.All",
"https://graph.microsoft.com/.default"
Below is the access token I am getting using the above code:
{"aud": "https://management.azure.com/",
"iss": "https://sts.windows.net/3863b7d0-213d-40f3-a4d0-6cd90452245a/",
"iat": 1628068305,
"nbf": 1628068305,
"exp": 1628072205,
"aio": "E2ZgYEjcvsaipUV1wxwxrne/9F4XAAA=",
"appid": "055eb578-4716-4901-861b-92f2469dac9c",
"appidacr": "1",
"idp": "https://sts.windows.net/3863b7d0-213d-40f3-a4d0-6cd90452245a/",
"oid": "33688cee-e16e-4d11-8ae0-a804805ea007",
"rh": "0.AUYA0LdjOD0h80Ck0GzZBFIkWni1XgUWRwFJhhuS8kadrJxGAAA.",
"sub": "33688cee-e16e-4d11-8ae0-a804805ea007",
"tid": "3863b7d0-213d-40f3-a4d0-6cd90452245a",
"uti": "nZUVod_e3EuO_T-Ter-_AQ",
"ver": "1.0",
"xms_tcdt": 1626687774
}
as you could see scope is not included in the token. Do I need to pass any other thing while getting the token.
Azure active directory set up:
registered application
2.Create client secret
3.Added redirect URL
added permission
Can someone please help me here, where I am doing mistake or is other any other way to make it work. Thank you
I can see a few issue here first your using the client credentials flow which requires that you assign Application Permission and you only have Delegate permission, with EWS the only Application permission that will work is full_access_as_app see https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth (app-only section)
String endpoint = String.format("https://login.microsoftonline.com/%s/oauth2/token", tenantId);
String postBody = String.format("grant_type=client_credentials&client_id=%s&client_secret=%s&resource=%s&scope=%s",
clientId, clientSecret, "https://management.azure.com/", scope);
Your mixing V1 and V2 authentication (see https://nicolgit.github.io/AzureAD-Endopoint-V1-vs-V2-comparison/) here which won't work (scope will just be ignored) for the v1 endpoint eg what you have in https://login.microsoftonline.com/%s/oauth2/token is the V1 auth endpoint so your request shouldn't include the scope just the resource and that resource should be https://outlook.office.com

unable to call a REST webservice..Full authentication required

I am currently working on spring application and REST webservices.
I have created a REST webservice in one application and want to access that service from other applications.
Below is the error its showing when trying to access the webservice.
RestClientException : org.springframework.web.client.HttpClientErrorException: 401 Full authentication is required to access this resource
Below is my webservice code:
#RequestMapping(value = MyRequestMapping.GET_ACC_DATA, method = RequestMethod.GET)
#ResponseBody
public MyResponseDTO getSigDataValues(#PathVariable final String acc, final HttpServletResponse response) throws Exception {
MyResponseDTO responseDTO = null;
try {
//logic goes here
//responseDTO = ..
} catch (Exception e) {
LOG.error("Exception" + e);
}
return responseDTO;
}
I am calling above webservice from another application.In the below mentioned method I am calling the webservice and its throwing me the exception org.springframework.web.client.HttpClientErrorException.
public MyResponseDTO getAccData(String acc){
try{
list= (List<String>)restTemplate.postForObject(MyDataURL.GET_ACC_DATA.value(), MyResponseDTO.class, acc);
}
catch (final RestClientException e)
{
LOG.info("RestClientException :" + e);
}
Please suggest, what am I missing.
You would need to authenticate against the REST service. One of the most common ways is Basic Authentication. If this is what the service is using you would need to create an AUTHORIZATION header with Base 64 encoded usernamen + password.
RestTemplate allow to set customer headers before the request gets sent.
The process of creating the Authorization header is relatively straightforward for Basic Authentication, so it can pretty much be done manually with a few lines of code:
private HttpHeaders createHeaders(String username, String password) {
return new HttpHeaders() {
private static final long serialVersionUID = -1704024310885506847L;
{
String auth = username + ":" + password;
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset.forName("US-ASCII")));
String authHeader = "Basic " + new String(encodedAuth);
set("Authorization", authHeader);
}
};
}
Then, sending a request becomes just as simple:
ResponseEntity<Dados> response = restTemplate.exchange(uriComponents.toUriString(), HttpMethod.GET,
new HttpEntity<Dados>(createHeaders(usuario, senha)), Dados.class);

while accessing profile data from google plus getting following exception my code is below

Exception in thread "main"
com.google.api.client.googleapis.json.GoogleJsonResponseException: 404
Not Found Not Found at
com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)
at
com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
at
com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:40)
at
com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:312)
at
com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1045)
at
com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:410)
at
com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:343)
at
com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:460)
at googleplusdemo2.MyClass.main(MyClass.java:107)
public class MyClass {
// List the scopes your app requires:
private static List<String> SCOPE = Arrays.asList(
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/plus.profiles.read",
"https://www.googleapis.com/auth/plus.circles.write",
"https://www.googleapis.com/auth/plus.stream.write",
"https://www.googleapis.com/auth/plus.stream.read");
// The following redirect URI causes Google to return a code to the user's
// browser that they then manually provide to your app to complete the
// OAuth flow.
private static final String REDIRECT_URI = "http://some url";
static String CLIENT_ID="myclient id";
static String CLIENT_SECRET="myclient secret";
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
new NetHttpTransport(),
new JacksonFactory(),
CLIENT_ID, // This comes from your Developers Console project
CLIENT_SECRET, // This, as well
SCOPE)
.setApprovalPrompt("force")
.setAccessType("offline").build();
String url = flow.newAuthorizationUrl().setRedirectUri(REDIRECT_URI).build();
System.out.println("Please open the following URL in your browser then " +
"type the authorization code:");
System.out.println(" " + url);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String code = br.readLine();
// End of command line prompt for the authorization code.
GoogleTokenResponse tokenResponse = flow.newTokenRequest(code)
.setRedirectUri(REDIRECT_URI).execute();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(new NetHttpTransport())
.setJsonFactory(new JacksonFactory())
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.addRefreshListener(new CredentialRefreshListener() {
#Override
public void onTokenResponse(Credential credential, TokenResponse tokenResponse) {
// Handle success.
System.out.println("Credential was refreshed successfully.");
}
#Override
public void onTokenErrorResponse(Credential credential,
TokenErrorResponse tokenErrorResponse) {
// Handle error.
System.err.println("Credential was not refreshed successfully. "
+ "Redirect to error page or login screen.");
}
})
.build();
// Set authorized credentials.
credential.setFromTokenResponse(tokenResponse);
credential.refreshToken();
// Create a new authorized API client
PlusDomains plusDomains = new PlusDomains.Builder(new NetHttpTransport(), new JacksonFactory(), credential).setApplicationName("TESTMK3").build();
Person mePerson = plusDomains.people().get("me").execute();
mePerson.getGender();
System.out.println("ID:\t" + mePerson.getId());
System.out.println("Display Name:\t" + mePerson.getDisplayName());
System.out.println("Image URL:\t" + mePerson.getImage().getUrl());
System.out.println("Profile URL:\t" + mePerson.getUrl());
}
}

Jersey Response for Exception not working as Expected

I am authenticating user with name and password from my database.
If the user name or password is incorrect then I am throwing a custom Exception.
but I am not getting expected status code and response.
I am new to Jersey and web services.
This is my function which creates a response :
public static Response error(int Status_code, String Request_status ,String data, String Response_error){
ResponseBuilder response = new ResponseBuilder();
response.Status_code = Status_code;
response.Request_status = Request_status;
response.data =data;
response.Response_error = Response_error;
return Response.status(Status_code).entity(response).build();
}
This is my custom Exception class code :
public class InvalidcredentialsException extends WebApplicationException {
public InvalidcredentialsException(Response response ) {
super(response);
}
}
This is how I am throwing this exception in my code if credentials are wrong(user name and password) in AuthenticateUser function in my Model.
throw new InvalidcredentialsException(ResponseBuilder.error(Response.Status.UNAUTHORIZED.getStatusCode(),"success","","invalid credentials"));
When I am checking my API I am getting 204 as response , but I am expecting a JSON with the parameters which I have provided.
I have implemented my API in the following way :
#Path("/user")
public class User {
#POST
#Path("/login")
#Consumes(MediaType.APPLICATION_JSON)
#Produces("application/json")
public void UserAuthentication(UserCredentials user) {
UserModel userAuthentication = new UserModel();
userAuthentication.AuthenticateUser(user);
}
}
I have used the following link to create Exception and throw :
JAX-RS / Jersey how to customize error handling?
This is my authenticate function :
public void AuthenticateUser(UserCredentials user) {
Database db = new Database();
Connection con = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;
try {
String username = user.getUsername();
String password = user.getPassword();
con = db.getConnection();
if (con != null) {
String selectQuery_UserDetails = "SELECT name,password FROM user WHERE name=? AND password = ?";
preparedStatement = con.prepareStatement(selectQuery_UserDetails);
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
rs = preparedStatement.executeQuery();
if (!rs.isBeforeFirst()) {
throw new InvalidcredentialsException(ResponseBuilder.error(Response.Status.UNAUTHORIZED.getStatusCode(),"success","","invalid credentials"));
}
}}catch (SQLException e) {
} finally {
db.closeConnection(con);
}
}
Thanks
You are catching but not handling the SQLException. When an error occurs, while accessing or trying to access the database, the exception is ignored and no error response is created. Maybe the database is not accessible or configured incorrectly.
You should wrap the exception into a RuntimeException like javax.ws.rs.InternalServerErrorException or just remove the catch statement. And you should log the error here or in an exception mapper, so that you are able to analyze and fix the problem.
I suggest to wrap the exception and log it like that:
}catch(SQLException e){
logger.log(Level.SEVERE, "db error while authenticate user", e);
throw new InternalServerErrorException("db error while authenticate user", e);
}
Now the runtime exception will be handled by a default exception mapper, which will generate the error response. Additional the error is logged. In this code I used java.util.logging - if necessary adjust it to the logging api you use.

HttpURLConnnection request failures on Android 6.0 (MarshMallow)

I am using Google's Volley library for my application project , that targets minimum api level 14.So, Volley library uses HttpURLConnection as the NetworkClient.
Therefore , there should not be any issue related to Removal of Apache HTTPClient. However, I have done the configuration required for 6.0 Sdk i.e compileSdkVersion 23, build-tool-version 23.0.1 and build:gradle:1.3.1' and even tried adding useLibrary 'org.apache.http.legacy'. Have updated the same for Volley library project in my application.
Recently ,I tried to run my app on Android 6.0 (MarshMallow), my project compiles and runs. But those requests that require authentication headers are failing on MarshMallow with:
BasicNetwork.performRequest: Unexpected response code 401 com.android.volley.AuthFailureError
However the same is running on all Api level below 23.
I have checked the headers many times.Strangely, those requests that do not require authentication are giving response with 200 OK.
Right now I am totally clueless what is breaking the requests, does anybody have any idea what has changed in new Version that HttpURLConnection request fails for only Api level 23? Is anybody else using Volley and facing similar issue?
Here is my CustomRequest Class
public class CustomRequest extends Request<Void> {
int id, cmd;
Map<String, String> params;
BaseModel model;
public CustomRequest(int method, int cmd, String url, Map<String, String> params, int id, BaseModel model) {
super(method, url, null);
this.id = id;
this.cmd = cmd;
this.params = params;
this.model = model;
if (method == Method.GET) {
setUrl(buildUrlForGetRequest(url));
}
Log.v("Volley", "Making request to: " + getUrl());
}
private String buildUrlForGetRequest(String url) {
if (params == null || params.size() == 0) return url;
StringBuilder newUrl = new StringBuilder(url);
Set<Entry<String, String>> paramPairs = params.entrySet();
Iterator<Entry<String, String>> iter = paramPairs.iterator();
newUrl.append("?");
while (iter.hasNext()) {
Entry<String, String> param = iter.next();
newUrl
.append(param.getKey())
.append("=")
.append(param.getValue());
if (iter.hasNext()) newUrl.append("&");
}
return newUrl.toString();
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = new HashMap<String, String>();
headers.put("X-Api-Version", Contract.API_VERSION);
headers.put("X-Client", "android");
String accessToken = APP.getInstance().getToken();
if (!accessToken.equals("")) {
headers.put("Authorization", "Bearer " + accessToken);
}
return headers;
}
#Override
protected Response<Void> parseNetworkResponse(NetworkResponse response) {
Exception ex;
try {
String jsonString = new String(
response.data, HttpHeaderParser.parseCharset(response.headers));
JsonNode json = new ObjectMapper().readTree(jsonString);
if (model != null) model.parse(id, json);
EventBus.getDefault().post(new EventResponse(cmd, true, null));
return Response.success(null, HttpHeaderParser.parseCacheHeaders(response)); //Doesn't return anything. BaseModel.parse() does all the storage work.
} catch (NoMoreDataException e) {
ex = e;
EventBus.getDefault().post(new NoMoreDataModel(cmd, e));
EventBus.getDefault().post(new EventResponse(cmd, false, null));
return Response.success(null, HttpHeaderParser.parseCacheHeaders(response));
} catch (Exception e) {
ex = e;
Log.e("CustomRequest", Log.getStackTraceString(e));
String message = APP.getInstance().getString(R.string.failedRequest);
if (!TextUtils.isEmpty(e.getMessage()))
message = e.getMessage();
EventBus.getDefault().post(new ErrorEventModel(cmd, message, e));
return Response.error(new ParseError(ex));
}
}
#Override
protected Map<String, String> getParams() throws AuthFailureError {
return params;
}
#Override
protected void deliverResponse(Void response) {
Log.v("Volley", "Delivering result: " + getUrl());
}
#Override
public void deliverError(VolleyError error) {
Log.e("CustomRequest", "Delivering error: Request=" + getUrl() + " | Error=" + error.toString());
String message = APP.getInstance().getString(R.string.failedRequest);
EventBus.getDefault().post(new ErrorEventModel(cmd, message, error));
}
}
Only difference I found between Api 23 and others is the HostNameVerifier.
For Api level 23 : com.android.okhttp.internal.tls.OkHostnameVerifier
For Api level <23 : javax.net.ssl.DefaultHostnameVerifier.
After checking the Server side of my application, found the reason behind the issue.
For Android 6.0(MarshMallow) the headers were becoming empty and this was not the case with other versions.
So the fix that worked for me:
Created and Added a new custom header X-Proxy-No-Redirect => 1 and passed along with other headers.
Theory behind it:
There is a API server to which we send request, then the API server redirects the request to corresponding site based on the oAuth token
While redirecting
For the redirection to happen, there are two ways to do that
1 - We just send a response back to the caller stating to redirect to a certain page. Then the caller(Networking library) takes care of redirection
2 - API server will itself makes the redirect request and get the response and then pass to caller
Earlier we were using the Method 1.
On Android 6.0 - The networking lib(Volley) doesn't seem to set all the headers while making the redirection request.
Once this new header is set, Method 2 will come into effective.
P.S This fix was applicable for my application project , maybe not for others.Just providing what was wrong and what helped me.

Resources