Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing Google Cloud Storage From Android OAuth2

I am trying to design an app which downloads the appropriate sound files from my Google cloud storage account. The app does not access the users account but my own.

My reading has led me to believe that the most appropriate model is The Service account https://code.google.com/p/google-api-java-client/wiki/OAuth2#Service_Accounts

Unfortunately the developers have decided not to provide an example with Android. They do provide a nice example with just plain Java, which works http://samples.google-api-java-client.googlecode.com/hg/storage-serviceaccount-cmdline-sample/instructions.html?r=default

I tried to adapt this for Android and have run into problems.

GoogleCredential credential =
        new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
            .setJsonFactory(JSON_FACTORY).setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
            .setServiceAccountScopes(STORAGE_SCOPE)
            .setServiceAccountPrivateKeyFromP12File(new File("key.p12")).build();

In Google's example they pass the key.p12 file , However on Android when I put the file in the res/raw folder it seems the only way I have of accessing it is as an input stream. I can't find an elegant way to get a file to pass to JSON.

This all leads me to believe I must be doing something wrong. Should I be using the key.p12 Should I be using the "service account model". Is there an example out there /

Thanks Ryan


An Update I managed to achieve my goal of getting it to work but my solution feels clunky to me and I am sure it is not the way intended

What I did was add the key.p12 as a raw/resource which I opened as as input stream. Which I then converted to the private key using the libraries as seen in the example.

http://www.flexiprovider.de/examples/ExampleSMIMEsign.html

My code looks like this

Security.addProvider(new de.flexiprovider.core.FlexiCoreProvider());
    // Next, we have to read the private PKCS #12 file, since the the
    // private key used for signing is contained in this file:
    DERDecoder dec = new DERDecoder(getResources().openRawResource(
            R.raw.key));
    PFX pfx = new PFX();
    try {
        pfx.decode(dec);
        SafeBag safeBag = pfx.getAuthSafe().getSafeContents(0)
                .getSafeBag(0);
        PKCS8ShroudedKeyBag kBag = (PKCS8ShroudedKeyBag) safeBag
                .getBagValue();
        char[] password = "my password from google api".toCharArray();
        privKey = kBag.getPrivateKey(password);
        new AsyncLoadStorage(this).execute();
    } catch (ASN1Exception e) {

But the whole thing is ugly and I would like a cleaner solution

like image 806
Ryan Heitner Avatar asked Aug 26 '12 14:08

Ryan Heitner


2 Answers

Google documentation is actually pretty misleading. They haven't updated their console links (keep on asking you to look for "register apps" link, which doesn't exist), and their JAVA api for GCS doesn't work in Android (GcsService service = GcsServiceFactory.createGcsService();). Seems like they expect you to access GCS from App engine only.

So, here's what one needs to do:

  • You probably don't want users to have to sign into GCS or Google (think of Snapchat app).
  • But you still want authentication, so your data isn't completely in public view; its only accessible through your apps.
  • You'd want to have a service account P2K file, which you can bundle along with app, so your users don't have to login anywhere.
  • From https://cloud.google.com/console/project, click Project > APIs & Auth > Credentials > Create new Client ID > Service Account. This would let you download P2K file.
  • Store this in res/raw/private_key.p2k, and then use the following code to authenticate your android app to query GCS.

This works without any problems for me:

        String STORAGE_SCOPE = "https://www.googleapis.com/auth/devstorage.read_write";
        JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();

        Log.d("testing", "checking if I can create a credential");
        httpTransport = AndroidHttp.newCompatibleTransport();
        KeyStore keystore = KeyStore.getInstance("PKCS12");
        keystore.load(resources_.openRawResource(R.raw.gcs_privatekey),
                "password".toCharArray());

        PrivateKey key = (PrivateKey) keystore.getKey("privatekey", "password".toCharArray());

        GoogleCredential credential = new GoogleCredential.Builder()
                .setTransport(httpTransport)
                .setJsonFactory(JSON_FACTORY)
                .setServiceAccountPrivateKey(key)
                .setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
                .setServiceAccountScopes(Collections.singleton(STORAGE_SCOPE))
                // .setServiceAccountUser(SERVICE_ACCOUNT_EMAIL)
                // .setClientSecrets(CLIENT_ID, CLIENT_SECRET)
                .build();
        credential.refreshToken();

        String URI = "https://storage.googleapis.com/" + BUCKET_NAME;
        HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential);
        GenericUrl url = new GenericUrl(URI);
        HttpRequest request = requestFactory.buildGetRequest(url);
        HttpResponse response = request.execute();
        String content = response.parseAsString();
        Log.d("testing", "response content is: " + content);
        new Storage.Builder(httpTransport, JSON_FACTORY, credential)
                .setApplicationName("appname").build();
like image 160
mrjn Avatar answered Oct 08 '22 01:10

mrjn


Here is what I did to transform the inputStream key to a PrivateKey object :

PrivateKey serviceAccountPrivateKey = SecurityUtils.loadPrivateKeyFromKeyStore(SecurityUtils.getPkcs12KeyStore(), MyClass.class.getResourceAsStream("/privatekey.p12"), "notasecret", "privatekey", "notasecret");

    // Build service account credential.
    GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
        .setJsonFactory(jsonFactory)
        .setServiceAccountId(ACCOUNT_ID)
        .setServiceAccountScopes(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL))
        //.setServiceAccountPrivateKeyFromP12File(f)
        .setServiceAccountPrivateKey(serviceAccountPrivateKey)
        .build();
like image 35
Frédérik Lacharité Avatar answered Oct 08 '22 00:10

Frédérik Lacharité