Tuesday, December 21, 2010

Android Google App Engine Authentication

Using GAE together with Android is nice because the Google account on the Android device can be used to authenticate users in a Google App Engine application. The user does not have to create a new login or enter username and password.

In order to make this work an authentication token has to be retrieved from Google Account on the device. Using this token a cookie from GAE is requested which can be used to authenticate HTTP connections.

Doing all this can be tricky so I implemented the following HttpContext which gets the authentication cookie and makes working with GAE easier.


package axegr.android.net.http;

import java.io.IOException;

import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.accounts.AccountsException;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import android.net.http.AndroidHttpClient;
import android.os.Bundle;

public final class AuthenticatedAppEngineContext implements HttpContext {
  private HttpContext delegate_;
  private CookieStore cookieStore_;

  public static HttpContext newInstance(Context context, String uri)
      throws AccountsException, AuthenticationException {
    if (context == null)
      throw new IllegalArgumentException("context is null");
    return new AuthenticatedAppEngineContext(context, uri);
  }

  private AuthenticatedAppEngineContext(Context context, String uri)
      throws AccountsException, AuthenticationException {
    delegate_ = new BasicHttpContext();
    String authToken = getAuthenticationToken(context);
    AndroidHttpClient httpClient = AndroidHttpClient.newInstance(
        "GetAuthCookieClient", context);
    try {
      httpClient.getParams().setBooleanParameter(
          ClientPNames.HANDLE_REDIRECTS, false);
      cookieStore_ = new BasicCookieStore();
      setAttribute(ClientContext.COOKIE_STORE, cookieStore_);
      HttpGet http_get = new HttpGet(uri
          + "/_ah/login?continue=http://localhost/&auth=" + authToken);
      HttpResponse response = httpClient.execute(http_get, this);
      checkResponse(cookieStore_, response);
    } catch (IOException e) {
      throw new AuthenticationException(
          "error getting the authentication cookie", e);
    } finally {
      httpClient.close();
    }

  }

  private void checkResponse(CookieStore cookieStore, HttpResponse response)
      throws AuthenticationException {
    if (response.getStatusLine().getStatusCode() != 302) {
      throw new AuthenticationException(
          "authentication response was not a redirect");
    }
    if (!isAuthenticationCookiePresent(cookieStore)) {
      throw new AuthenticationException(
          "authentication cookie not found in cookie store");
    }
  }

  private String getAuthenticationToken(Context context)
      throws AccountsException {
    AccountManager accountManager = AccountManager.get(context);
    Account[] accounts = accountManager.getAccountsByType("com.google");

    if (accounts == null || accounts.length == 0) {
      throw new AccountsException(
          "no account of type 'com.google' found on this device");
    }

    try {
      Account account = accounts[0];
      AccountManagerFuture<Bundle> accountManagerFuture = accountManager
          .getAuthToken(account, "ah", true, null, null);
      Bundle authTokenBundle = null;
      authTokenBundle = accountManagerFuture.getResult();
      String authToken = authTokenBundle
          .get(AccountManager.KEY_AUTHTOKEN).toString();
      return authToken;

    } catch (OperationCanceledException e) {
      throw new AccountsException(
          "could not get authentication token from account 'com.google'",
          e);
    } catch (AuthenticatorException e) {
      throw new AccountsException(
          "could not get authentication token from account 'com.google'",
          e);
    } catch (IOException e) {
      throw new AccountsException(
          "could not get authentication token from account 'com.google'",
          e);
    }
  }

  private boolean isAuthenticationCookiePresent(CookieStore cookieStore) {
    for (Cookie cookie : cookieStore.getCookies()) {
      if (cookie.getName().equals("ACSID")
          || cookie.getName().equals("SACSID"))
        return true;
    }
    return false;
  }

  public Object getAttribute(String id) {
    return delegate_.getAttribute(id);
  }

  public Object removeAttribute(String id) {
    return delegate_.removeAttribute(id);
  }

  public void setAttribute(String id, Object obj) {
    delegate_.setAttribute(id, obj);
  }

}


And here is an example on how to use it:


    AndroidHttpClient client = AndroidHttpClient.newInstance(
        "IntegrationTestAgent", this.getContext());
    try {
      String baseUri = "https://android-gae-http-test.appspot.com";
      HttpContext httpContext = AuthenticatedAppEngineContext
          .newInstance(this.getContext(), baseUri);
      HttpGet get = new HttpGet(baseUri + "/authenticated/get");
      HttpResponse response = client.execute(get, httpContext);
      String result = EntityUtils.toString(response.getEntity());
    } finally {
      client.close();
    }

Saturday, November 27, 2010

Android Color Spinner

A quick hack for choosing colors with a Spinner. The color names are not displayed, instead the values of a string array are parsed as color values.




Lets start with the spinner declaration:

Spinner spinner = (Spinner) findViewById(R.id.color_spinner);
  ArrayAdapter adapter = new ArrayAdapter(this,
  R.layout.color_spinner_item, new String[] { "#F5F6F6",
    "#FFC0CB", "#FF0000", "#C00000" });
  adapter.setDropDownViewResource(R.layout.color_spinner_dropdown_item);
  spinner.setAdapter(adapter);

Next thing is the layout definitions for the item and the dropdown item:

color_spinner_dropdown_item.xml
<view xmlns:android="http://schemas.android.com/apk/res/android"
       android:id="@android:id/text1"
       class="net.eggeral.CheckedColorTextView" 
       style="?android:attr/spinnerDropDownItemStyle"
       android:singleLine="true"
       android:layout_width="fill_parent"
       android:layout_height="?android:attr/listPreferredItemHeight"
       android:ellipsize="marquee" />

color_spinner_item.xml
<view
  class="net.eggeral.ColorTextView" 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"/>

And two custom views for item and dropdown item:

CheckedColorTextView.java
package net.eggeral;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.CheckedTextView;

public class CheckedColorTextView extends CheckedTextView {
public CheckedColorTextView(Context context) {
super(context);
}

public CheckedColorTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public void onDraw(Canvas canvas) {
int color = Color.parseColor(getText().toString());
Paint paint = new Paint();
paint.setColor(color);
setTextColor(color);

canvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()), 5, 5,
paint);
super.onDraw(canvas);
}
}

ColorTextView.java
package net.eggeral.marmind;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.TextView;

public class ColorTextView extends TextView {
public ColorTextView(Context context) {
super(context);
}

public ColorTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.parseColor(getText().toString()));

canvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()), 5, 5,
paint);
}
}

Friday, August 20, 2010

Wait for ExtJs Ajax Request to Finish with Selenium

To wait for an ExtJs Ajax call to finish in Selenium the following statement can be used:


_selenium.WaitForCondition("selenium.browserbot.getCurrentWindow().Ext.Ajax.isLoading() == false;", _defaultTimeout);

Monday, June 28, 2010

Powershell Remote Execution (invoke-command) with Username Password Authentication

Powershell adds remote execution with 2.0. The commands are executed on the remote shell using the current local user or some other domain user specified using the -credential parameter.

Using just username and password is not as easy and of course not very secure because the password is saved in the script. Nevertheless sometimes you just have to and here is how.

$pass = convertto-securestring "P@ssW0rd" -asplaintext -force
$mycred = new-object -typename System.Management.Automation.PSCredential -argumentlist "DOMAIN\User",$pass
invoke-command remote-machine {get-process} -credential $mycred

Tuesday, June 22, 2010

Start Stop IIS7 or Any Other Service With Powershell

The IIS7 service can be controlled via Powershell using the following commandline.

(Get-WmiObject Win32_Service -Filter "Name='w3svc'").InvokeMethod("StopService",$null)

Use StopService to stop the service and StartService to start the service.

Use any other service name instead of w3svc to start or stop an arbitrary service.