Securing Spring RESTful webservice APIs from unautherized access? - android

I have successfully created a Spring RESTful webservice with different APIs. Now I should protect them from unauthorized access. I followed http://www.beingjavaguys.com/2014/10/spring-security-oauth2-integration.html and the login logic is entirely different from mine. Can someone help me to move on?
Fetch user login request
#RequestMapping(value = "/login", method = RequestMethod.POST)
#ResponseBody
#ResponseStatus(HttpStatus.OK)
public UserResponse login(#RequestBody final UserLoginRequest userRequest) throws ServletException, IOException {
UserResponse userResponse = new UserResponse();
try {
userResponse = accessService.login(userRequest);
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return userResponse;
}
Process user login request
#Transactional
public UserResponse login(UserLoginRequest userRequest) throws SQLException,
ClassNotFoundException, IOException {
UserResponse userResponse = new UserResponse();
int status = 0;
//boolean isExist = loginDao.isUserExist(userRequest.getUsername(), userRequest.getPassword());
User user = loginDao.getUser(userRequest.getEmailID());
if (user != null) {
if (userRequest.getPassword().equals(user.getPassword())) {//Case sensitive password and added to check status
//User exist
if (user.getStatus().equals("1")) {
//Device token check
loginDao.isDeviceTokenExists(userRequest, user.getProfileId());
status = 2;
} else {
status = 3;
}
} else {
status = 4;
}
} else {
status = 1;
}
if (status == 1) {
userResponse.setCode(WeekenterConstants.USER_EMAIL_EXIST_CODE);
userResponse.setMessage("User does not exists.Please Register.");
} else if (status == 2) {
userResponse.setCode(WeekenterConstants.SUCCESS_CODE);
userResponse.setMessage("User login success");
userResponse.setId(user.getProfileId());
} else if (status == 3) {
userResponse.setCode(WeekenterConstants.FAILURE_CODE);
userResponse.setMessage("Your Account is blocked. Please contact Weekenter administrator.");
userResponse.setId(user.getProfileId());
} else if (status == 4) {
userResponse.setCode(WeekenterConstants.FAILURE_CODE);
userResponse.setMessage("Password is wrong.");
userResponse.setId(user.getProfileId());
}
return userResponse;
}
I have API's for fetch countries, userlist etc. Those services should only give data to the Android client once the user is valid. I know the authentication will be processed by using access token. How could I do it in a standard way?

I think you need to have a separate process that will authorize a device for use in your application.
I have worked on an application in which tablets are registered for using an app. The tablet ID is saved in a simple text file that is accessible to the Apache server. Then all REST requests have a special header X_DEVICEID which contains the device ID, and a PHP script used by Apache checks for this ID in the file, and will only give a response if the ID is for a registered device.
The file of allowed device IDs acts as a sort of firewall to block unregistered devices.

You can follow the mentioned tutorial itself by changing the login
logic in your service.define a custom authentication service in your
spring-security.xml.
Typically, a simple Spring Security enabled application would use a
simple user service as the authentication source:
<!--Custom User details service which is provide the user data-->
<bean id="customUserDetailsService"
class="com.yourpackage.CustomUserDetailsService" />
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="customUserDetailsService" />
</authentication-manager>
Your customUserDetailsService should implement UserDetailsService
available in
org.springframework.security.core.userdetails.UserDetailsService
import com.weekenter.www.dao.LoginDao;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
#Service
#Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private LoginDao loginDao;
public UserDetails loadUserByUsername(String login)
throws UsernameNotFoundException {
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
com.weekenter.www.entity.User user = null;
try {
user = loginDao.getUser(login);//login variable contain your requested username
if (user != null) {
if (user.getStatus().equals("1")) {
enabled = false;
}
} else {
throw new UsernameNotFoundException(login + " Not found !");
}
} catch (Exception ex) {
try {
throw new Exception(ex.getMessage());
} catch (Exception ex1) {
}
}
<!-- Password comparison will happen here -->
return new User(
user.getEmail(),
user.getPassword(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities()
);
}
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authList = getGrantedAuthorities(getRoles());
return authList;
}
public List<String> getRoles() {
List<String> roles = new ArrayList<String>();
roles.add("ROLE_APP");
return roles;
}
public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}
And finally in spring-security.xml you can filter protected URL's
like below
<!-- This is where we tells spring security what URL should be protected
and what roles have access to them -->
<http pattern="/api/**" create-session="never"
entry-point-ref="oauthAuthenticationEntryPoint"
access-decision-manager-ref="accessDecisionManager"
xmlns="http://www.springframework.org/schema/security">
<anonymous enabled="false" />
<intercept-url pattern="/api/**" access="ROLE_APP" />
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>

Related

Programmatically Auto Accept Incoming Bluetooth Files

I am trying to find a way to have a tablet basically auto-accept/give permission to accept an incoming Bluetooth Share file transferred from a laptop to my Nexus Android device. Since the Android bluetooth system itself does not support this feature, I was wondering if this could be done programmatically using an Android application that listened for the notification and accepted it for me.
MUCH EASIER WAY
If you have a rooted device and use XPosed Framework, your goal can be achieved much easier.
You Need not implement your own bluetooth server nor kill the original BT service, which are very bothering!!!
xposed tutorial link.
Try this code.
import android.util.*;
import de.robv.android.xposed.*;
import de.robv.android.xposed.callbacks.XC_LoadPackage.*;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class Tutorial implements IXposedHookLoadPackage
{
private String TAG="TUTORIAL";
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.android.bluetooth"))
{
Log.i(TAG,"Not: "+lpparam.packageName);
return;
}
Log.i(TAG,"Yes "+lpparam.packageName);
findAndHookMethod("com.android.bluetooth.opp.BluetoothOppManager", lpparam.classLoader, "isWhitelisted", String.class,new XC_MethodHook() {
#Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Log.v(TAG,"HOOK DONE");
param.setResult(true); /* you can compare the sender address(String) with your computer and determine if you return true or just allow the original method to be called after this returns.*/
}
});
}
}
I tested and it works fine:)
Links
Dropbox link of the auto accepting app
Dropbox link of the project files (zip)
Xposed apk site
Towelroot site to root your phone
Background(Original answer)
As I commented above, you bay be able to, and I tried and succeeded in blocking (though not receiving) with this code.
import android.util.*;
import de.robv.android.xposed.*;
import de.robv.android.xposed.callbacks.XC_LoadPackage.*;
import java.io.*;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class Tutorial implements IXposedHookLoadPackage
{
private String TAG="TUTORIAL";
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.android.bluetooth"))
{
Log.i(TAG,"Not: "+lpparam.packageName);
return;
}
Log.i(TAG,"Yes "+lpparam.packageName);
findAndHookMethod("com.android.bluetooth.opp.BluetoothOppService", lpparam.classLoader, "startSocketListener", new XC_MethodHook() {
#Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Log.v(TAG,"HOOK DONE");
param.setResult(null);
}
});
}
}
The code above hooks the method startListenerSocket() of com.android.bluetooth.BluetoothOppService and prevents the original method from being called by the line param.setResult(null);
Refer to here to see the full code of com.android.bluetooth.BluetoothOppService.java and you will understand the operation.
And the code you can start from is shown below.
import android.util.*;
import de.robv.android.xposed.*;
import de.robv.android.xposed.callbacks.XC_LoadPackage.*;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class Tutorial implements IXposedHookLoadPackage
{
private String TAG="TUTORIAL";
public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.android.bluetooth"))
{
Log.i(TAG,"Not: "+lpparam.packageName);
return;
}
Log.i(TAG,"Yes "+lpparam.packageName);
findAndHookMethod("com.android.bluetooth.opp.BluetoothOppObexServerSession", lpparam.classLoader, "onPut", new XC_MethodHook() {
#Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Log.v(TAG,"HOOK DONE");
Class c=param.thisObject.getClass();
}
});
}
}
This code hooks the onPut method of com.android.bluetooth. BluetoothOppObexServerSession linked here. I either am newbie to xposed framework but I hope my answer helped.
I had the same issues you asked and partially solved the problem by implementing my custom OBEX server and manually / programmatically(with ps|grep and su kill pid) killing the native BluetoothOppService. But I will either try the idea of hooking and directly executing my code.
And to help you customize OBEX server session I post my implementation below.
#Override
public int onPut(Operation op)
{
if (D)
{
Log.d(TAG, "onPut " + op.toString());
}
HeaderSet request;
String name, mimeType;
Long length;
String extension=null;// type;
int obexResponse = ResponseCodes.OBEX_HTTP_OK;
String destination;
if (mTransport instanceof BluetoothObexTransport)
{
destination = ((BluetoothObexTransport) mTransport).getRemoteAddress();
}
else
{
destination = "FF:FF:FF:00:00:00";
}
boolean isWhitelisted =IsWhitelisted(destination);
try
{
boolean preReject = false;
request = op.getReceivedHeader();
if (V)
{
// Constants.logHeader(request);
}
name = (String) request.getHeader(HeaderSet.NAME);
length = (Long) request.getHeader(HeaderSet.LENGTH);
mimeType = (String) request.getHeader(HeaderSet.TYPE);
if (length == 0)
{
if (D)
{
Log.w(TAG, "length is 0, reject the transfer");
}
preReject = true;
obexResponse = ResponseCodes.OBEX_HTTP_LENGTH_REQUIRED;
}
if (name == null || name.isEmpty())
{
if (D)
{
Log.w(TAG, "name is null or empty, reject the transfer");
}
preReject = true;
obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
int dotIndex = name.lastIndexOf(".");
if (dotIndex > 0)
{
extension = name.substring(dotIndex + 1).toLowerCase();
}
// Reject policy: anything outside the "white list" plus unspecified
// MIME Types. Also reject everything in the "black list".
// if (!preReject && (mimeType == null || (!isWhitelisted && !Constants.mimeTypeMatches(
// mimeType, Constants.ACCEPTABLE_SHARE_INBOUND_TYPES))
// || Constants.mimeTypeMatches(mimeType,
// Constants.UNACCEPTABLE_SHARE_INBOUND_TYPES))) {
// if (D) {
// Log.w(TAG, "mimeType is null or in unacceptable list, reject the transfer");
// }
// preReject = true;
// obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;
// }
if (preReject && obexResponse != ResponseCodes.OBEX_HTTP_OK)
{
// some bad implemented client won't send disconnect
return obexResponse;
}
}
catch (IOException e)
{
Log.e(TAG, "get getReceivedHeaders error " + e);
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
int status = receiveFile(destination, name, extension, length, op);
/*
* TODO map status to obex response code
*/
if (status != BluetoothShare.STATUS_SUCCESS)
{
obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
Log.d(TAG, "MIME TYPE)" + mimeType);
return obexResponse;
}
I just removed some rejecting codes from the original one.
Also to look at my full code please refer to my git repository.
I also thank the contributors to the android project!

Using cached Cognito identity from Xamarin

When I first log into my app, I go through the following code:
auth = new Xamarin.Auth.OAuth2Authenticator(
"my-google-client-id.apps.googleusercontent.com",
string.Empty,
"openid",
new System.Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new System.Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new System.Uri("https://www.googleapis.com/oauth2/v4/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
Which triggers this activity:
[Activity(Label = "GoodleAuthInterceptor")]
[IntentFilter(actions: new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataSchemes = new[] { "com.enigmadream.storyvoque" }, DataPaths = new[] { "/oauth2redirect" })]
public class GoodleAuthInterceptor : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Android.Net.Uri uri_android = Intent.Data;
Uri uri_netfx = new Uri(uri_android.ToString());
MainActivity.auth?.OnPageLoading(uri_netfx);
Finish();
}
}
And finally this code to link the account to Cognito:
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = "us-east-2:79ebf8e1-97de-4d1c-959a-xxxxxxxxxxxx";
cli.GetIdAsync(req).ContinueWith((task) =>
{
if ((task.Status == TaskStatus.RanToCompletion) && (task.Result != null))
{
ShowMessage(string.Format("Identity {0} retrieved", task.Result.IdentityId));
}
else
ShowMessage(task.Exception.InnerException != null ? task.Exception.InnerException.Message : task.Exception.Message);
});
}
else
ShowMessage("Login cancelled");
}
This all works great, and after the login, I am able to use my identity/credentials to retrieve data from DynamoDB. With this object:
Amazon.DynamoDBv2.AmazonDynamoDBClient ddbc = new Amazon.DynamoDBv2.AmazonDynamoDBClient(credentials, RegionEndpoint.USEast2);
The second time I run my app, this code runs:
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
ShowMessage(string.Format("I still remember you're {0} ", idToken));
And if I try to use the credentials with DynamoDB (or anything, I assume) at this point, I get errors that I don't have access to the identity. I have to logout (credentials.Clear()) and login again to obtain proper credentials.
I could require that a user go through the whole login process every time my app runs, but that's a real pain because the Google login process requires the user to know how to manually close the web browser to get back to the application after authenticating. Is there something I'm missing about the purpose and usage of cached credentials? When I use most apps, they aren't requiring me to log into my Google account every time and close a web browser just to access their server resources.
It looks like the refresh token needs to be submitted back to the OAuth2 provider to get an updated id token to add to the credentials object. First I added some code to save and load the refresh_token in a config.json file:
private Dictionary<string, string> config;
const string CONFIG_FILE = "config.json";
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
if (e.Account.Properties.ContainsKey("refresh_token"))
{
if (config == null)
config = new Dictionary<string, string>();
config["refresh_token"] = e.Account.Properties["refresh_token"];
WriteConfig();
}
credentials.AddLogin("accounts.google.com", idToken);
CognitoLogin(idToken).ContinueWith((t) =>
{
try
{
t.Wait();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
ShowMessage("Login cancelled");
}
void WriteConfig()
{
using (var configWriter = new System.IO.StreamWriter(
Application.OpenFileOutput(CONFIG_FILE, Android.Content.FileCreationMode.Private)))
{
configWriter.Write(ThirdParty.Json.LitJson.JsonMapper.ToJson(config));
configWriter.Close();
}
}
public void Login()
{
try
{
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
if (ReadConfig())
{
LoginRefreshAsync().ContinueWith((t) =>
{
try
{
t.Wait();
if (!t.Result)
FullLogin();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
{
credentials.Clear();
FullLogin();
}
}
}
else
FullLogin();
bDidLogin = true;
}
catch(Exception ex)
{
ShowMessage(string.Format("Error logging in: {0}", ex.Message));
}
}
private bool ReadConfig()
{
bool bFound = false;
foreach (string filename in Application.FileList())
if (string.Compare(filename, CONFIG_FILE, true) == 0)
{
bFound = true;
break;
}
if (!bFound)
return false;
using (var configReader = new System.IO.StreamReader(Application.OpenFileInput(CONFIG_FILE)))
{
config = ThirdParty.Json.LitJson.JsonMapper.ToObject<Dictionary<string, string>>(configReader.ReadToEnd());
return true;
}
}
Then refactored the code that initiates the interactive login into a separate function:
public void FullLogin()
{
auth = new Xamarin.Auth.OAuth2Authenticator(CLIENTID_GOOGLE, string.Empty, "openid",
new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new Uri("https://accounts.google.com/o/oauth2/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
}
Refactored the code that retrieves a Cognito identity into its own function:
private async Task CognitoLogin(string idToken)
{
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = ID_POOL;
try
{
var result = await cli.GetIdAsync(req);
ShowMessage(string.Format("Identity {0} retrieved", result.IdentityId));
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
}
And finally implemented a function that can retrieve a new token based on the refresh token, insert it into the current Cognito credentials, and get an updated Cognito identity.
private async Task<bool> LoginRefreshAsync()
{
string tokenUrl = "https://accounts.google.com/o/oauth2/token";
try
{
using (System.Net.Http.HttpClient client = new System.Net.Http.HttpClient())
{
string contentString = string.Format(
"client_id={0}&grant_type=refresh_token&refresh_token={1}&",
Uri.EscapeDataString(CLIENTID_GOOGLE),
Uri.EscapeDataString(config["refresh_token"]));
System.Net.Http.HttpContent content = new System.Net.Http.ByteArrayContent(
System.Text.Encoding.UTF8.GetBytes(contentString));
content.Headers.Add("content-type", "application/x-www-form-urlencoded");
System.Net.Http.HttpResponseMessage msg = await client.PostAsync(tokenUrl, content);
string result = await msg.Content.ReadAsStringAsync();
string idToken = System.Json.JsonValue.Parse(result)["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
/* EDIT -- discovered this is not necessary! */
// await CognitoLogin(idToken);
return true;
}
}
catch (Exception ex)
{
ShowMessage(ex.Message);
return false;
}
}
I'm not sure if this is optimal or even correct, but it seems to work. I can use the resulting credentials to access DynamoDB without having to prompt the user for permission/credentials again.
There's a very different solution I'm trying to fit with the other answer. But it's so different, I'm adding it as a separate answer.
It appears the problem was not so much related to needing to explicitly use a refresh token to get an updated access token (I think this is done implicitly), but rather needing to remember the identity token. So rather than include all the complexity of manually applying a refresh token, all that's needed is to store the identity token (which can be done in a way similar to how the refresh token was being stored). Then we just need to add that same identity token back to the credentials object when it's missing.
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (config.Read())
{
if (config["id_token"] != null)
credentials.AddLogin(currentProvider.Name, config["id_token"]);
Edit: The problem of needing to use a refresh token does still exist. This code works if the token hasn't expired, but attempting to use these credentials after the token has expired will fail, so there is still some need to use a refresh token somehow in some cases.

SignalR HTTP status 400 multiple clients

I'm running an application with SignalR 2.2.0 on server side and signalr-java-client (self compiled, last GitHub version) on Android as client.
Currently, there are 4 clients connected to my hub. From time to time, it happens, that all 4 clients simultaneously receive the HTTP status 400 with the message "The connection id is in the incorrect format" (the clients were connected before). I analyzed this multiple times and am not able to find any information/pattern when or why this happens.
The connecten is secured via JWT, the token is definitely valid. When retrieving a new token, the connection is stopped and started again. Apart from this, it is very unlikely that the error is device-related, because the error is thrown at all 4 clients the same time.
I know, this error can occur when the client's Identity changes, but an Identity change for 4 clients the same time seems very unlikely to me.
This is the server-code used for authentication (Deepak asked).
The following method gets called in my Startup.cs:
public static void ConfigureOAuth(IAppBuilder app, string audienceID, string sharedSecret)
{
byte[] secret = TextEncodings.Base64Url.Decode(sharedSecret);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
Provider = new MyOAuthBearerAuthenticationProvider(),
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceID },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(Issuer, secret)
}
});
}
Here's the code of MyOAuthBearerAuthenticationProvider class:
class MyOAuthBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
/// <summary>
/// Get's a JWT from querysting and puts it to context
/// </summary>
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context.Token == null)
{
string value = context.Request.Query.Get("auth_token");
if (!string.IsNullOrEmpty(value)) //token from queryString
{
context.Token = value;
}
}
return Task.FromResult<object>(null);
}
}
I have to retrieve the token from query string, because additionally to the java-client, a javascript client is used, which is not able to set headers.
Lastly, I secure my hub and some of it's methods with the Authorization attribute:
[Authorize(Roles = "MyExampleRole")]
This is the client-code for connection:
public boolean connect(String url, String token) {
if (connected) {
return true;
}
try {
this.hubConnection = new HubConnection(url, "auth_token=" + token, true, logger);
this.hubProxy = hubConnection.createHubProxy("MyHub");
this.hubProxy.subscribe(this.signalRMethodProvider);
this.hubConnection.stateChanged(stateChangedCallback);
SignalRFuture<Void> awaitConnection = this.hubConnection.start();
awaitConnection.get(10000, TimeUnit.MILLISECONDS);
return true;
}
catch (InterruptedException | TimeoutException | ExecutionException e) {
log.error("connect", e);
return false;
}
}
Does anybody have an Idea, how to fix this problem or where I may receive further information?
Thank you very much
-Lukas
seems fine...
possible alteration you can do is change
awaitConnection.get(10000, TimeUnit.MILLISECONDS);
to
awaitConnection.done(new Action<Void>() {
#Override
public void run(Void obj) throws Exception {
Log.d(TAG, "Hub Connected");
}
}).onError(new ErrorCallback() {
#Override
public void onError(Throwable error) {
error.printStackTrace();
Log.d(TAG, "SignalRServiceHub Cancelled");
}
}).onCancelled(new Runnable() {
#Override
public void run() {
Log.d(TAG, "SignalRServiceHub Cancelled");
}
});

Couchbase facebook pull authenticator

I am using couchbase mobile for an application and I want to use facebook for authentication. As per documentation, couchbase offers it's own implementation for authentication, the only required thing would be the token which I retrieve from the android facebook login flow.
The code for Synchronize class looks something like this:
public class Synchronize {
public Replication pullReplication;
public Replication pushReplication;
public static class Builder {
public Replication pullReplication;
public Replication pushReplication;
public Builder(Database database, String url, Boolean continuousPull) {
if (pullReplication == null && pushReplication == null) {
URL syncUrl;
try {
syncUrl = new URL(url);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
pullReplication = database.createPullReplication(syncUrl);
pullReplication.setContinuous(true);
pushReplication = database.createPushReplication(syncUrl);
pushReplication.setContinuous(true);
}
}
public Builder facebookAuth(String token) {
if (!TextUtils.isEmpty(token)) {
Authenticator facebookAuthenticator = AuthenticatorFactory.createFacebookAuthenticator(token);
pullReplication.setAuthenticator(facebookAuthenticator);
pushReplication.setAuthenticator(facebookAuthenticator);
}
return this;
}
public Builder basicAuth(String username, String password) {
Authenticator basicAuthenticator = AuthenticatorFactory.createBasicAuthenticator(username, password);
pullReplication.setAuthenticator(basicAuthenticator);
pushReplication.setAuthenticator(basicAuthenticator);
return this;
}
public Builder addChangeListener(Replication.ChangeListener changeListener) {
pullReplication.addChangeListener(changeListener);
pushReplication.addChangeListener(changeListener);
return this;
}
public Synchronize build() {
return new Synchronize(this);
}
}
private Synchronize(Builder builder) {
pullReplication = builder.pullReplication;
pushReplication = builder.pushReplication;
}
public void start() {
pullReplication.start();
pushReplication.start();
}
public void destroyReplications() {
if (pullReplication != null && pushReplication != null) {
pullReplication.stop();
pushReplication.stop();
pullReplication.deleteCookie("SyncGatewaySession");
pushReplication.deleteCookie("SyncGatewaySession");
pullReplication = null;
pushReplication = null;
}
}
}
And I use it like this:
...
public void startReplicationSync(String facebookAccessToken) {
if (sync != null) {
sync.destroyReplications();
}
final String url = BuildConfig.URL_HOST + ":" + BuildConfig.URL_PORT + "/" + DATABASE_NAME;
sync = new Synchronize.Builder(databaseManager.getDatabase(), url, true)
.facebookAuth(facebookAccessToken)
.addChangeListener(getReplicationChangeListener())
.build();
sync.start();
}
...
My sync gateway json config file:
{
"interface":":4984",
"adminInterface":":4985",
"log":["REST"],
"facebook":{
"register" : true
},
"databases":{
"sync_gateway":{
"server":"http://localhost:8091",
"bucket":"sync_gateway",
"users": {
"GUEST": {"disabled": false}
},
"sync":`function(doc) {channel(doc.channels);}`
}
}
}
I also tried with "GUEST": {"disabled": true}, no luck
My problem is that if I do this
pullReplication.setAuthenticator(facebookAuthenticator);
pushReplication.setAuthenticator(facebookAuthenticator);
Nothing will ever get replicated/pulled from the server. However if I don't set an authenticator, everything is pulled. Is it something I am doing wrong? I really need to use the authenticator in order to prevent some documents to not being replicated for non-authenticated users.
Note! The token is good, as if I am looking in the users section of sync gateway admin, I can see the right profile id of the logged in user token I passed to the couchbase facebook authenticator.
In the Sync Gateway config you provided, the Sync Function is function(doc, oldDoc) {channel(doc.channels);} which means that if the document processed by Sync Gateway contains a string(s) under the channels field, the document will be mapped to this/these channel(s). Let's assume the following config file:
{
"log": ["CRUD"],
"databases": {
"db": {
"server": "walrus:",
"users": {
"GUEST": {"disabled": false, "admin_channels": ["*"]}
},
"sync": `
function sync(doc, oldDoc) {
channel(doc.channels);
}
`
}
}
}
If the channels field doesn't exist then the document will be mapped to a channel called undefined. But the GUEST account has access to the * channel (a placeholder to represent all channels). So, all unauthenticated replications will pull all documents. Let's now introduce the facebook login field in the config file. This time, replications authenticated with a facebook token represent a new user which has only access to the ! channel by default (watch this screencast to understand the ! channel, a.k.a the public channel https://www.youtube.com/watch?v=DKmb5mj9pMI). To give a user access to other channels, you must use the access API call in the Sync Function (read more about all Sync Function API calls here).
In the case of facebook authentication, the user's facebook ID is used to represent the user name. Supposing that the document has a property holding the user's facebook ID (user_id: FACEBOOK_ID), you can map the document to a channel and give the user access to it. The new Sync Function would look like this:
function(doc, oldDoc) {
channel(doc._id);
access(doc.user_id, doc._id);
}
You can retrieve the user's facebook ID with the Facebook Android SDK and save on a document field.

Authenticate with Google Plus, with android App, how to get REFRESH TOKEN

Im triying to authenticate with google, I´m currently using the way i´t is recomended on his documentation, but.. is there any EASY way to get the refresh token?, I make the auth and get the token, but it have been impossible for me to take the refresh token , and I need id.
I have tried lots of ways, I have spend more than a week with this issue, is it possible to get that token? I´ve tried with lots of manuals, tutorials... but I can´t.
Anyone Knows any place where I can Know how to get the resfresh_token and it is good explained and that is currently working?.
Thanks a lot!!
Pd: is a native android App.
EDIT:
Ok, for More info, I´m making the auth as is in google´s documentation to auth with GoogleApiClient with little variations( because I´m using it as a cain of manager) . THIS PART RUN´S OK:
Firs instead of calling on create I call:
public void logginGooglePlus(GooglePlusAuthCallback googlePlusAuthCallback) {
gPAuthCallback = googlePlusAuthCallback;
// Initializing google plus api client
String scope = "audience:server:client_id:" + SERVER_CLIENT_ID;
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).addApi(Plus.API)
.addScope(Plus.SCOPE_PLUS_LOGIN).build();
mGoogleApiClient.connect();
mSignInClicked = true;
signInWithGplus(gPAuthCallback);
}
I continue just with copy&paste with the google´s:
#Override
public void onConnectionFailed(ConnectionResult result) {
if (!result.hasResolution()) {
// GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),
// this,
// 0).show();
if (gPAuthCallback != null) {
gPAuthCallback.onLoginError(result.toString());
}
return;
}
if (!mIntentInProgress) {
// Store the ConnectionResult for later usage
mConnectionResult = result;
if (mSignInClicked) {
// The user has already clicked 'sign-in' so we attempt to
// resolve all
// errors until the user is signed in, or they cancel.
resolveSignInError();
}
}
}
#Override
public void onActivityResult(int requestCode, int responseCode,
Intent intent) {
super.onActivityResult(requestCode, responseCode, intent);
if (requestCode == RC_SIGN_IN) {
if (responseCode != RESULT_OK) {
mSignInClicked = false;
}
mIntentInProgress = false;
if (!mGoogleApiClient.isConnecting()) {
mGoogleApiClient.connect();
}
}
}
#Override
public void onConnected(Bundle arg0) {
mSignInClicked = false;
// Get user's information
if (gPAuthCallback != null) {
gPAuthCallback.onLoginSuccesful();
}
}
#Override
public void onConnectionSuspended(int arg0) {
mGoogleApiClient.connect();
}
/**
* Sign-in into google
* */
public void signInWithGplus(GooglePlusAuthCallback googlePlusAuthCallback) {
gPAuthCallback = googlePlusAuthCallback;
if (!mGoogleApiClient.isConnecting()) {
mSignInClicked = true;
resolveSignInError();
}
}
/**
* Method to resolve any signin errors
* */
private void resolveSignInError() {
if (mConnectionResult.hasResolution()) {
try {
mIntentInProgress = true;
mConnectionResult.startResolutionForResult(this, RC_SIGN_IN);
} catch (SendIntentException e) {
mIntentInProgress = false;
mGoogleApiClient.connect();
}
}
}
And finally I call to get the persons data:
public void getProfileInformation(
GooglePlusGetPersonCallback getPersonCallback) {
this.googlePlusGetPersonCallback = getPersonCallback;
try {
if (Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null) {
currentPerson = Plus.PeopleApi
.getCurrentPerson(mGoogleApiClient);
String personName = currentPerson.getDisplayName();
String personPhotoUrl = currentPerson.getImage().getUrl();
String personGooglePlusProfile = currentPerson.getUrl();
String email = Plus.AccountApi.getAccountName(mGoogleApiClient);
Log.e("GPlus", "Name: " + personName + ", plusProfile: "
+ personGooglePlusProfile + ", email: " + email
+ ", Image: " + personPhotoUrl);
new getTokenAsyncTask().execute();
}
} catch (Exception e) {
if (googlePlusGetPersonCallback != null) {
// googlePlusGetPersonCallback.ongeGooglePersonError(e.getCause()
// .toString());
}
e.printStackTrace();
}
}
Ok, leaving here is easy, now It starts the funny part: I need the Refresh Token because I have to sign in with a server, and I have to pass the access_token, refresh_token and user_id.
reading this: https://developers.google.com/accounts/docs/CrossClientAuth
I understand that I have to make the getToken call with a different Scope, so I change it: the method for get token is :
// GET TOKEN 2o plano
public class getTokenAsyncTask extends AsyncTask<Void, Boolean, String> {
#Override
protected String doInBackground(Void... params) {
try {
String acountname = Plus.AccountApi
.getAccountName(mGoogleApiClient);
// agregamos el scope del server para que me loguee para la app
// "crossclient"
String serverScope = "audience:server:client_id:"
+ SERVER_CLIENT_ID;
String token = GoogleAuthUtil.getToken(GooglePlusManager.this,
acountname, serverScope);
return token;
} catch (UserRecoverableAuthException e) {
// startActivityForResult(e.getIntent(), "NECESITA AUT");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace(); // TODO: handle the exception
}
return null;
}
#Override
protected void onPostExecute(String code) {
String token = code;
if (googlePlusGetPersonCallback != null) {
googlePlusGetPersonCallback.ongeGooglePersonSuccesful(
currentPerson, token);
}
}
}
According to documentation, with this I´ll get a token that: "The ID token will contain several data fields", and I´m only retrieving a string token ( but it does not give any crash or issue so I suppose it is Ok). I haven´t got access to the Server, but I´ve suppose that it is ok, because the ios app is running ok already ( another company have done it in Ios), do I have to ask them to make in the server any thing so I can authenticate my android app with the server?
The ios app is passing to the server the parameter I´ve already said (acces, refres, id) So I Imagine that I have to pass the same in android, I have acces to the console and I have declared the android app in the same project.
well, from the part I am, that I have an supposed valid token.. how can I get the refresh token? I´m completely lost...
If anyone knows how to get it.. I´ll invite as much beer as you can ( I have lost so many hours with this :S ).
xcuses for the really really big post :( ( it´s my first one!).
According to the google documentation you can exchange tokens.
So, if you post required parameters to below link, then you will obtain a refresh token
https://www.googleapis.com/oauth2/v3/token
Parameters,
var params = {
code: 'FROM ANDROID [ONE TIME CODE]',
client_id: 'FROM YOUR GOOGLE CONSOLE',
client_secret: 'FROM YOUR GOOGLE CONSOLE',
redirect_uri: 'FROM YOUR GOOGLE CONSOLE',
grant_type: 'authorization_code'
access_type:'offline'
};
The only thing you must send from android is one-time code. Other parameters are static, store they in a config file.
var params = {
code: 'FROM ANDROID [ONE TIME CODE]',
Android side, Enable server-side API access for your app, after implementing this, you will have an one-time code
I hope these will be helpful for you.

Categories

Resources