I'm trying to make a mobile app that, eventually in one of its activities will connect to a Blob storage in Android to transfer a collection of images.
But I couldnt even get there, since one of the requirements is making that connection to be safe, since the client could be anyone from his mobile phone, so my first step here is requesting a SAS token prior to start any transaction.
So this is basically two steps that I'm following,
1 -> Implemented an Azure Function inside my App Service that returns a SAS token (I got that function from here: Sas Token Function
)
2 -> Trying to call that function from my Android Code and get my SAS token.
Looks really easy and I'm sure it is, the function in the link explains the required http body to ask for concrete access, and I think is there where I'm failing, below is my code to call the function:
private void getStringFromAzure() throws MalformedURLException {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("container", "uploadedimages");
jsonObject.addProperty("permissions", "Write, Create");
// Create the MobileService Client object and set your backend URL
String yourURL = "https://mydirectory.azurewebsites.net/";
MobileServiceClient mClient = new MobileServiceClient(yourURL, this);
// Your query pointing to yourURL/api/values
ListenableFuture<JsonElement> query = mClient.invokeApi("GetSasToken-Net", jsonObject, "POST", null);
// Callback method
Futures.addCallback(query, new FutureCallback<JsonElement>() {
#Override
public void onSuccess(JsonElement jsonElement) {
final String result = jsonElement.toString();
// Since you are on a async task, you need to show the result on the UI thread
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(mContext, result, Toast.LENGTH_LONG).show();
}
});
}
#Override
public void onFailure(Throwable throwable) {
Log.d(TAG, "onFailure: " + throwable.getMessage());
}
});
}
The code is failing in "InvokeApi" line function, where throws an exception before even warning me that is going to connect to Internet from mobile.
Another thing that I think could be wrong is that, nowhere in the Azure Get Sas Token Function is specifying my account credentials ( I didnt developed that function, but should work fine as it is, it anyways doesnt let you change anything, imported via GitHub )
I have really small base/background in this kind of things and Im sure I'm missing something (or "lots" of somethings), but I really appreciate a hand, this is driving me crazy.
PD. Internet permissions already given in manifest.
Thank you all in advance, first post in StackOverflow after following for many years!
Related
I got a message from the Google Play Console saying that
Your app includes a WebView that is vulnerable to cross app scripting. Please see this Google Help Center article for details. Vulnerable classes: com.company.main.company.MainActivity->onCreate
The error points me to this document on how to fix the issue
It gives me 2 options. I cannot use Option 1 because it kills my app when changing android:exported=false
So I am stuck with option 2. One of the things it says is to
Ensure that parameters to evaluateJavascript are always trusted. Calling evaluateJavascript using unsanitized input from untrusted Intents lets attackers execute harmful scripts in the affected WebView.
The way I am using evaluateJavascript in my webView is I am getting the HTML of a div with id user_id_firebase to determine the apps state of whether the user has logged out of my app or they are logged on. Then with the firebase ID I can update my DB with the users/apps token for notifications.
Code
webView.evaluateJavascript(
"(function() { return (document.getElementById('user_id_firebase').innerHTML); })();",
new ValueCallback<String>() {
#Override
public void onReceiveValue(String html) {
html = html.replaceAll("[\"]+", "");
StringTokenizer currentString = new StringTokenizer(html, "|");
final String first = currentString.nextToken().trim();
final String second = currentString.nextToken().trim();
// Write a message to the database
database = FirebaseDatabase.getInstance().getReference();
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener( MainActivity.this, new OnSuccessListener<InstanceIdResult>() {
#Override
public void onSuccess(InstanceIdResult instanceIdResult) {
String newToken = instanceIdResult.getToken();
if (first.equals("gone")) {
database.child("users").child(second).child("android").child(getMacAddr()).removeValue();
} else if (first.equals("none")) {
database.child("users").child(second).child("android").child(getMacAddr()).removeValue();
} else if (first.equals("active")) {
database.child("users").child(second).child("android").child(getMacAddr()).setValue(newToken);
}
}
});
}
});
Because I am getting the HTML of that div from document.getElementById I am assuming it is referring to this input which is what I believe I need to sanitize.
How do I sanitize this input to make the Google Gods happy? I have seen there are several ways to do this, but is there a specific way I need to do it that will get my app to be approved once again?
We are using:
BMSClient.getInstance().registerAuthenticationListener("realm", new CustomAuthentication(this));
and:
AuthorizationManager.createInstance(this.getApplicationContext());
AuthorizationManager.getInstance().setAuthorizationPersistencePolicy(AuthorizationManager.PersistencePolicy.ALWAYS);
to store the authorization data on the phone. "The authorization data will be saved on local storage" is set to ALWAYS.
The code above is always run on our splashscreen on startup so it is always run when the app restarted.
The problem we have had is that after some time (hours or days) when what we suspect the token has expired we get response in the form of HTTP 307. Even after restarting the app we keep getting this response on our requests. The only way to get around it is to go into the app from settings and clear all data.
The following questions would help us go forward in our testing and possible solution:
How long is the token cached in BMSClient? (testing purposes)
Can AuthorizationManager help us in any way to force a new fetch of token?
Are they working on log out functionality?
Our custom listener:
public class CustomAuth implements AuthenticationListener {
private Context activityContext;
public CustomAuth(Context activityContext) {
this.activityContext = activityContext;
}
#Override
public void onAuthenticationChallengeReceived(AuthenticationContext authContext, JSONObject challenge, Context context) {
//1. read the challenge JSONObject
//2. handle the challenge (use the context for handling UI based operations)
//3. return response using the AuthenticationContext authContext
SharedPreferences preferences = activityContext.getSharedPreferences("UserPreference", Context.MODE_PRIVATE);
String email = preferences.getString("email", "");
if(email.equals("")) {
email = "unidentified-user#error.com";
}
JSONObject jsonEmail = new JSONObject();
try {
jsonEmail.put("email", email);
} catch (JSONException e) {
authContext.submitAuthenticationChallengeAnswer(null);
}
authContext.submitAuthenticationChallengeAnswer(jsonEmail);
}
#Override
public void onAuthenticationSuccess(Context context, JSONObject info) {
//additional operations in case of authentication success
Log.d("Authentication", "Auth success: " + String.valueOf(info));
}
#Override
public void onAuthenticationFailure(Context context, JSONObject info) {
//additional operations in case of authentication failure
Log.d("Authentication", "Auth failure ." + String.valueOf(info));
}
}
Have you tried using AuthorizationManager.clearAuthorizationData() API when 307 is received and resending the request?
To answer your questions:
1) The authorization token will be cached indefinitely. The token expires after a 60 minute time period, but will remain cached until a new token is obtained.
It is best practice to obtain a new token once the original token has expired. This can be accomplished by running a new authorization challenge once the previous token has expired.
2) You can always use AuthorizationManager to obtain a new token once the previous token has expired by accessing a protected resource, using obtainAuthorizationHeader, etc.
3) There is currently no way to log out of MCA using the AuthorizationManager. I will speak with the development team about future plans.
In regards to the main problem I see in your question. I would expect you are experiencing this problem because you are attempting to use an expired token against the authorization service. Even though the token is still cached on the device it expired an hour after creation. I would attempt to run a new authorization challenge against the MCA service once the token expires.
If you want to provide your code that may also help me investigate further.
I am trying to get the response from instagram api using volley, but can't get the data. I did't receive any call back methods like onResponse or onErrorResponse. Nothing show up. I Could not see any error.
here is my code.
public String getUserId(String usrName) {
url = TContants.urlBeforeUserId + usrName + TContants.urlAfterUser;
JsonObjectRequest jsonObjReq;
jsonObjReq = new JsonObjectRequest(Method.GET, url,
null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
TagsResponse gsonData = gson.fromJson(response.toString(), TagsResponse.class);
userId = gsonData.data[0].id.toString();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("Volley:", "getUserId response error");
}
});
AppController.getInstance().addToRequestQueue(jsonObjReq,
TContants.tag_json_obj);
return userId;
}
url is working fine, I tested it.
When code running JsonObjectRequest creating. but next step it skip the onResponse and onErrorResponse methods. plz help.
The code inside onResponse is not skipped, that's how it's supposed to work, what you are looking at is a Callback.
A very quick and general explanation would be:
this code does not run serially, instead, onResponse in this case is your implementation of an interface provided by the request object, that will be called when the response arrives, this might take several milliseconds to seconds (depending on the server, since this callback is for a network operation).
Read about callback handling (both network, and the simple ones you create with interfaces - and if you haven't yet - read about interfaces), as it is a major part of programming.
ADDITION:
To see when the response does return, I would print all the parameters before sending them to make sure they are sent correctly, and also print the response itself (response.toString() at the beginning of onResponse) and wait a bit to see it after the request is sent.
(don't be alarmed if the print won't contain readable info, it depends on the implementation of the .toString() method, for now it's just an indication that you got a response at all).
I need to identify user who made request to my endpoint api, via Android client. Though I am able to follow best practice by keeping my Api and App within one project using gradle and android studio. Also I am able to send request to my endpoint api and receive response without authorization.
Basically I need to send authorization token as header in the request, people suggest that, merely by adding instance of "GoogleAccountCredential" along with the request will do the trick as in the code below. The class in which below code is present that extends android.os.AsyncTask; I have been following https://cloud.google.com/appengine/docs/java/endpoints/consume_android#using_the_account_picker, but code fragments are not very clear.
#Override
protected String doInBackground(Pair<Context, String>... params) {
..
MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(), new AndroidJsonFactory(), credential).setRootUrl("https://myapp.appspot.com/_ah/api/");
..
What I have :
I have an Activity called ExpandedListViewActivity
another thing is ExpandedListAdaptor, which populates views dynamically ( form ).
My Objective
When User clicks on submit present in the form.
Android should be able to find the google account and its credentials and attach it with the request.
If it does not find then show account selector view, so that user can select account, if we can do it silently without user consent that would be very nice.
Extra methods that I have in :
class EndpointsAsyncTask extends AsyncTask, Void,
String>
void chooseAccount() {
mActivity.startActivityForResult(credential.newChooseAccountIntent(),
REQUEST_ACCOUNT_PICKER);
}
protected String fetchToken() throws IOException {
try {
return GoogleAuthUtil.getToken(mActivity, mEmail, mScope);
} catch (UserRecoverableAuthException userRecoverableException) {
// GooglePlayServices.apk is either old, disabled, or not present
// so we need to show the user some UI in the activity to recover.
userRecoverableException.printStackTrace();
} catch (GoogleAuthException fatalException) {
// Some other type of unrecoverable exception has occurred.
// Report and log the error as appropriate for your app.
}
return null;
}
public void getSettings(){
Log.d(APP, "get Settings ");
settings = mActivity.getSharedPreferences("Api", 0);
credential = GoogleAccountCredential.usingAudience(mActivity,
"server:client_id:Android-clientId.apps.googleusercontent.com");
setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
}
// setSelectedAccountName definition
private void setSelectedAccountName(String accountName) {
SharedPreferences.Editor editor = settings.edit();
editor.putString(PREF_ACCOUNT_NAME, accountName);
editor.commit();
credential.setSelectedAccountName(accountName);
this.accountName = accountName;
}
Please take into account that my endpoint server side is properly configured and running.
It should be straight forward, but I am not able to solve this, Please point the mistake or show me a direction to solve this..
Thanks for reading.
Shashank
This solves my problem.
http://android-developers.blogspot.in/2013/01/verifying-back-end-calls-from-android.html
https://cloud.google.com/appengine/docs/java/endpoints/auth
Specify the client IDs (clientIds) of apps authorized to make requests to your API backend.
Add a User parameter to all exposed methods to be protected by authorization.
Generate the client library again for any Android clients
Redeploy your backend API. <-- This was the key, to solve this problem.
Thanks,
Shashank
I am relatively new to android and facebook so please bear with me. IMPORTANT NOTE: Wherever I type h.. that means http://www. I'm not intending to post links here but I have to in order to explain this (my permission only allows 2 links) so please bear with me.
This app does a facebook post using the FacebookDialog.ShareDialogBuilder. This all works great now IF the image for the post using the .setPicture method is given a static hardcoded URL h..example.com/share_name/image_name.png. In that case the post works and the picture shows up on the post and everything is fine.
However, the image sent to the post is dynamically created by the app. Therefore I am sending the image to facebook's staging area which also works fine.
The Request.newUploadStagingResourceWithImageRequest returns a response that has the JSON encoded URI of the location of
the image in facebook's staging area.
The problem is that the FacebookDialog.ShareDialogBuilder doesn't like that URI location. Somehow it's not formed properly or something or I'm just doing something wrong.
Here are the details and what I've tried:
1) uriMine, the location where the image gets stored, as it is originally returned from facebook's staging resource upload call is:
"fbstaging://graph.facebook.com/staging_resources/MDE4NTY0NzE4MDQ0MTUwNjA6MTM5ODI2Nzc3Ng==". I don't know what the protocol "fbstaging:" is all about (I searched and searched online but nothing) but I
ran the app as is with that at first. The result was, well, unpredictable results apparently, as it got stuck in a loop (the looper class kept repeating in no particular pattern). It would show the post screen but you couldn't type in a message as it would lock up, close, repeat etc...
2) After getting a little education online about well formed URL's I replaced the fbstaging:// with h.. and thus changed the uriMine variable to the following:
h..graph.facebook.com/staging_resources/MDE4NTY0NzE4MDQ0MTUwNjA6MTM5ODI2Nzc3Ng==
This solved the endless loop problem (made the post work fine) except it would not show any image.
3) To see if it would work with any old normal URL of the form h..blablabla.com/image_resource I hardcoded URL's of a few images online and it worked fine, and showed the images.
4) Ok, I promise, I'm all most done (whew!). So, where it stands right now is:
a) passing uriMine as fbstaging://graph.facebook.com/staging_resources/etc etc
makes it freak out.
b) sending a normal URL of an online resource works fine (formed as a browser forms it, by the way).
c) prepending http://www. instead of the fbstaging:// makes the post work but facebook doesn't show the image, as if it can't find it.
By the way, going directly to the above by copy/pasting it into a browser gets redirected to the following:
h..dnsrsearch.com/index.php?origURL=http%3A//www.graph.facebook.com/staging_resources/MDE4NTY0NzE4MDQ0MTUwNjA6MTM5ODI2Nzc3Ng%3D%3D&r=
as apparently it can't find it.
SO:
What is it about that URI that is wrong or what am I missing? Please help.
Thank you very much for your time and patience reading this.
public class FacebookActivity extends Activity {
// initialize the global object to enable passing of activity to facebook dialog
public GlobalClass globalObject = new GlobalClass();
private UiLifecycleHelper uiHelper; // for Facebook...to mimic android's activity life cycle
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
uiHelper = new UiLifecycleHelper(this, null);
uiHelper.onCreate(savedInstanceState);
// set the calling activity...to pass to Face book
globalObject.setCurrentActivity(this);
// start Facebook Login
Session currentSession = new Session(this);
currentSession = Session.openActiveSession(this, true, new Session.StatusCallback() {
// callback when session changes state
#Override
public void call(final Session session, SessionState state, Exception exception) {
// this callback should fire multiple times, be sure to get the right one i.e. session.isOpened()
if (session.isOpened()) {
// make request to the /me API
Request.newMeRequest(session, new Request.GraphUserCallback() {
// callback after Graph API response with user object
#Override
public void onCompleted(GraphUser user, Response response) {
if (user != null) {
Bitmap bitmap = takeScreenshot();
Request imageRequest = Request.newUploadStagingResourceWithImageRequest(Session.getActiveSession(), bitmap, new Request.Callback() {
#Override
public void onCompleted(Response response) {
String uriMine = "";
JSONObject data = response.getGraphObject().getInnerJSONObject();
try {
uriMine = data.getString("uri");
uriMine = "http://www." + uriMine.substring(12); // strip off the "fbstaging://" from the uri
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (FacebookDialog.canPresentShareDialog(getApplicationContext(),
FacebookDialog.ShareDialogFeature.SHARE_DIALOG))
{
FacebookDialog shareDialog = new FacebookDialog.ShareDialogBuilder(globalObject.getCurrentActivity())
.setLink("https://play.google.com/store")
.setPicture(uriMine)
.setRequestCode(NativeProtocol.DIALOG_REQUEST_CODE)
.setApplicationName("This is the App Name")
.setName("This is the name")
.setDescription("This is the description")
.setCaption("This is the caption")
.build();
uiHelper.trackPendingDialogCall(shareDialog.present());
}
else
{
Toast.makeText(globalObject.getCurrentActivity(), "Please install the Facebook App first from Google Play.", Toast.LENGTH_LONG).show();
}
}
});
imageRequest.executeAsync();
}
}
}).executeAsync();
}
}
});
}
The staging resource endpoint is only used for staging binary data for open graph objects or actions, and is not meant for the regular link shares. See the documentation here: https://developers.facebook.com/docs/reference/android/current/class/Request/#newUploadStagingResourceWithImageRequest
In this case, you can either use the PhotoShareDialogBuilder (but then you can't add a link), or you can upload the image to your own hosting service, and use an http/https url in the setPicture method.