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?
Related
I am implementing AWS with an Android application for the first time.
We would like to use Cognito to authenticate our users, and selectively provide data from DynamoDB.
I have successfully set up my user pool and can see new registrations appear in the user list. Trying to login with an email that does not exist fails.
However, Cognito always logs in with a valid email address, regardless of password input.
What is wrong with my process?
public class CognitoController extends Application {
static CognitoUserPool pool;
static String userEmail;
public void onCreate(){
super.onCreate();
pool = new CognitoUserPool(this,
"us-east-xxxx",
"xxxx",
"xxxx",
new ClientConfiguration(),
Regions.US_EAST_1);
}
}
-
private void actionAdminLogin(){
UtilityInterfaceTools.hideSoftKeyboard(AdminLoginActivity.this);
String inputEmail = ((EditText) findViewById(R.id.input_admin_email)).getText().toString();
String inputPassword = ((EditText) findViewById(R.id.input_admin_password)).getText().toString();
CognitoController.userEmail = inputEmail;
details = new AuthenticationDetails(inputEmail, inputPassword, null);
AuthenticationHandler auther = new AuthenticationHandler() {
#Override
public void onSuccess(CognitoUserSession userSession, CognitoDevice newDevice) {
Toast.makeText(AdminLoginActivity.this, "Congratulations It Works...", Toast.LENGTH_LONG).show();
startActivity(new Intent(AdminLoginActivity.this, AdminPortalActivity.class));
finish();
}
#Override
public void getAuthenticationDetails(AuthenticationContinuation continuation, String email) {
continuation.setAuthenticationDetails(details);
continuation.continueTask();
}
#Override
public void getMFACode(MultiFactorAuthenticationContinuation continuation) {
continuation.continueTask();
}
#Override
public void authenticationChallenge(ChallengeContinuation continuation) {
continuation.continueTask();
}
#Override
public void onFailure(Exception exception) {
TextView errorMessage = findViewById(R.id.message_invalid_credentials);
errorMessage.setText(exception.toString());
errorMessage.setVisibility(View.VISIBLE);
}
};
CognitoController.pool.getUser(inputEmail).getSessionInBackground(auther);
}
I think your problem (which is not a problem by the way) is either:
In your pool Cognito setting, you chose your devices to be remembered.
Remembered
devices are also tracked. During user authentication, the key and secret pair assigned to a remembered device is used to authenticate the device to verify that it is the same device that the user previously used to sign in to the application. APIs to see remembered devices have been added to new releases of the Android, iOS, and JavaScript SDKs. You can also see remembered devices from the Amazon Cognito console.
The token is already cached:
Caching
The Mobile SDK for Android caches the last successfully authenticated user and the user's tokens locally on the device, in SharedPreferences. The SDK also provides methods to get the last successfully authenticated user.
Your Application Update
In fact for better user experience, you want the user to use the app, and don't need to login every time that she wants to use your app (e.g., look at mail apps, social media apps, etc.). However, you application need to handle that, you have two choices here:
Redirect to login if necessary: If the user is already logged in and wants to use the application again, your app needs to verify the user against the Cognito user pool, and only then, redirect the user to the login page if necessary.
Remove the token: If you really want the user to login every time that she uses the application, then remove the token if the user signs out; but I do not recommend this, for the sake of user experience.
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!
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.
I have code from a previous Android app which I successfully integrated with Twitter. I've copied this code over to a new app and changed the callback-url, consumer-key and consumer-secret for my new app.
Using the twitter4j library I'm able to get my RequestToken and authentication url as follows:
Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(myConsumerKey, myConsumerSecret);
RequestToken requestToken = twitter.getOAuthRequestToken(myCallbackUrl);
String authenticationUrl = requestToken.getAuthenticationURL()
The RequestToken has a non-null token value, a non-null tokenSecret value, and a null secretKeySpec value. The authentication url is of the following form:
http://api.twitter.com/oauth/authenticate?oauth_token=...
I load this url in my WebView and I see the following page:
Here I'm stuck. When I click the Sign In button, just this same page keeps loading up. Nothing else. When I click the Cancel button however, I see the following page:
Only on this page when I click the Return to Fan League Beko BBL button is my callback-url invoked, with a denied parameter which has my oauth_token as its value. Anyone seen anything like this before and know what might be stopping my sign in request from being processed???
Update 1: I've tried the authentication url and the Sign In button from a desktop browser and it works as expected, processing the response and then invoking the callback-url. It's just failing when trying it in the WebView of my Android app. I've tried it on multiple Android devices and tablets. JavaScript is enabled on my WebView too so that's not it. Ideas most welcome. I'm out of ideas!
Update 2: I've just done some debug-mode code-tracing on my WebView's WebViewClient and I'm seeing that when I click the Sign In button, the shouldOverrideUrlLoading(WebView webView, String url) method is not called, but the following three methods are called (in order): onPageStarted(WebView view, String url, Bitmap favicon), onLoadResource(WebView view, String url), onPageFinished(WebView view, String url). The url value these methods have is: https://api.twitter.com/oauth/authenticate, i.e. the oauth_token parameter has been stripped off the authenticate url. Maybe this is a POST request being treated as a GET request and hence why this one same page keeps loading up? Any ideas what I can do to have this Sign In button press processed properly?
I guess you forgot to set a callback url from twitter app control panel.
Log into twitter api section, choose your app and go to settings tab.
If you don't, when the user press login no redirect will happen and thus you will not be able to catch the verifier.
When you set a callback url on the other hand, your webview can intercept the redirect.
NOTE: You can set whatever url you want, the important thing is to catch the oauth_verifier parameter passed to the redirect url.
In that case your
shouldOverrideUrlLoading
should be triggered.
Override onLoadResource(WebView view, String url) from WebViewClient and use this code inside Authorization Activity where you use webView.loadUrl(authenticationUrl) in onResume() and webView.setWebViewClient(webViewClient) in onCreate().
private WebViewClient webViewClient = new WebViewClient() {
#Override
public void onLoadResource(WebView view, String url) {
// the URL we're looking for looks like this:
// callbackurl?oauth_token=1234567890qwertyuiop
Uri uri = Uri.parse(url);
if (uri.getHost().equals("callbackurlhost")) {
String token = uri.getQueryParameter("oauth_token");
if (null != token) {
webView.setVisibility(View.INVISIBLE);
AccessToken accessToken = twitter.getOAuthAccessToken();
// TODO store access token
finish();
} else {
// TODO tell user to try again
}
} else {
super.onLoadResource(view, url);
}
}
};
I have integrated sharing on twitter in my application recently. Its working perfectly.
I have used latest twitter4j-core-3.0.3.jar
I have created a simple demo application, you can download it from following links and go thorugh it.
http://santhoshkumaar.blogspot.in/2013/02/posting-message-on-twitter.html
https://github.com/santhoshkumaar/ShareOnTwitter/
This might be helpful to you.
Strangely my Twitter integration has started working. I didn't change my application code or my application's Twitter settings. I've noticed that where the Twitter authenticate page had a blue Sign In button previously, it now has a blue Authenticate app button. So I'm guessing something was changed/fixed on Twitter's end.
Have you put the callback url?
public static final String CALLBACK_URL = "twitterapp://connect";
check your authorize method:
mHttpOauthConsumer = new CommonsHttpOAuthConsumer(mConsumerKey, mSecretKey);
mHttpOauthprovider = new DefaultOAuthProvider("http://twitter.com/oauth/request_token",
"http://twitter.com/oauth/access_token",
"http://twitter.com/oauth/authorize");
public void authorize()
{
mProgressDlg.setMessage("Initializing ...");
mProgressDlg.show();
new Thread() {
#Override
public void run() {
String authUrl = "";
int what = 1;
try {
authUrl = mHttpOauthprovider.retrieveRequestToken(mHttpOauthConsumer, CALLBACK_URL);
what = 0;
Log.d(TAG, "Request token url " + authUrl);
} catch (Exception e) {
Log.d(TAG, "Failed to get request token");
e.printStackTrace();
}
mHandler.sendMessage(mHandler.obtainMessage(what, 1, 0, authUrl));
}
}.start();
}
make call to twitter.authorize();
Hope this helps