1
votes

Greetings my fellow developers,

It is already 3 days I'm struggling with a connection to the Cosmos DB without success.

I'm using retrofit as my REST client and GsonConverterFactory for serialization/deserialization.

Current status is that I get HTTP:400 (bad request) from Cosmos DB REST API. I have tried to adopt the authentication header generation from this SO answer

So here is my code (this unit test is runnable from your development environment. please see gradle.build lines to run it in the bottom of this post):

@RunWith(AndroidJUnit4.class)
@MediumTest
public class AzureDbConnectionTests {
    public static final int COSMOS_PORT_NUM = 443;
    public static final String COSMOS_DB_URL = "https://mazedb.documents.azure.com";
    public static final String CONNECTION_STR = 
            COSMOS_DB_URL + ":" + COSMOS_PORT_NUM;
    public static final String PRIMARY_KEY = 
"<Private Key>";

    // Entity to serialize into Cosmos DB
    public static class Building {
        public Building() {}

        private String mName;
        private String mAddress;
        private String id;
    }

    public interface FirstAzureService {
        @POST("/dbs/mazedb/colls/buildings/docs")
        Call<Building> addDocument(
            @Header("authorization") String authorization, 
            @Header("x-ms-date") String date, 
            @Body Building building);
    }

    @Test
    public void serverConnectionTest() throws Exception {
        String headerDate = getDateString();

        Building building = new Building();
        building.mName = "UUID";
        building.id = UUID.randomUUID().toString();

        Retrofit retrofit = new Retrofit.Builder().baseUrl(CONNECTION_STR)
            .addConverterFactory(GsonConverterFactory.create()).build();

        FirstAzureService azureService = retrofit.create(FirstAzureService.class);

        Call<Building> buildingCall = azureService.addDocument(
            generateAuthHeader("post", "docs", "dbs/mazedb/colls/buildings",
            headerDate, PRIMARY_KEY), headerDate, building);

        Response<Building> response = buildingCall.execute();
        Log.d("AzureDbConnectionTest", "HTTP status code: " + response.code());
        Log.d("AzureDbConnectionTest", "HTTP message: " + response.message());
        Log.d("AzureDbConnectionTest", headerDate);
        assertTrue(response.isSuccessful());
    }

    private String generateAuthHeader(String verb, String resourceType, String resourceId, String headerDate, String masterKeyBase64) throws Exception
    {
        //Decode the master key, and setup the MAC object for signing.
        byte[] masterKeyBytes = Base64.decode(PRIMARY_KEY, Base64.NO_WRAP);
        Mac mac = Mac.getInstance("HMACSHA256");
        mac.init(new SecretKeySpec(masterKeyBytes, "HMACSHA256"));

        //Build the unsigned auth string.
        String stringToSign = verb.toLowerCase() + "\n"
                + resourceType.toLowerCase() + "\n"
                + resourceId.toLowerCase() + "\n"
                + headerDate.toLowerCase() + "\n"
                + "\n";

        //Sign and encode the auth string.
        String signature = Base64.encodeToString(
            mac.doFinal(stringToSign.toLowerCase().getBytes("UTF8")), Base64.NO_WRAP);

        //Generate the auth header.
        String authHeader = 
            URLEncoder.encode("type=master&ver=1.0&sig=" + signature, "UTF8");

        return authHeader;
    }

    @NonNull
    public static String getDateString() {
        SimpleDateFormat formatter = 
            new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
        formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
        return formatter.format(new Date()).toLowerCase();
    }
}

The API is SQL API. The DB data as appears on Azure Dashboard: db structure

I have also tried to find some available Cosmos DB client for android to save time on fiddling with the authentication and the rest. But could find only this: https://github.com/Azure/Azure.Android. Which is exactly what I'm looking for but it is still under development and lacks MongoDB API (I thought to use MongoDB API for easier switch if it will come).

Many thanks for any help! I'm so tired of this.

P.S. List of HTTP Status Codes for Azure Cosmos DB could be found on official website. From there reasons for code 400 are:

  • The JSON, SQL, or JavaScript in the request body is invalid.
  • In addition, a 400 can also be returned when the required properties of a resource are not present or set in the body of the POST or PUT on the resource.
  • 400 is also returned when the consistent level for a GET operation is overridden by a stronger consistency from the one set for the account.
  • 400 is also returned when a request that requires an x-ms-documentdb-partitionkey does not include it.

I think most probable is wrong JSON, but after serialization of the same object in another unit test I find it OK: {"id":"cceb3f5d-8d9c-44cd-85ee-599cd2f58783","mName":"UUID"}

Kindest regards, Greg.

build.gradle to run it:

dependencies {
    androidTestCompile "junit:junit:4.12"
    androidTestCompile "com.android.support:support-annotations:25.3.1"
    androidTestCompile "com.android.support.test:runner:0.5"
    androidTestCompile "com.android.support.test:rules:0.5"
    androidTestCompile "com.google.code.gson:gson:2.8.2"
    androidTestCompile "com.squareup.retrofit2:retrofit:2.4.0"
    androidTestCompile "com.squareup.retrofit2:converter-gson:2.4.0"
}
1
You must immediately change your account keys!Gaurav Mantri
@GauravMantri I edited it out but i think he rejected the editNick Chapsas
I could be missed the edit. These keys are not important. The DB is just for kick-start of the project. Of course, I will change the keys later. Thanks.Gregory Stein
@GregoryStein People can change the offer and start spamming documents to charge you money.Nick Chapsas
@Elfocrash fyi even if you edit out someone's keys (which should be done), anyone with enough rep can read edit history and see the keys, which is why it's critical to change keys when accidentally publishing legit, current keys.David Makogon

1 Answers

1
votes

At the generateAuthHeader method you are providing dbs/mazedb/colls/buildings as the String resourceId which is the collection id. This is wrong.

You should change it to buildings.