SQL-запрос CosmosDb Rest с использованием жетонов ресурсов - PullRequest
0 голосов
/ 06 декабря 2018

У меня есть мобильный клиент Xamarin Forms, с которым я хочу напрямую поговорить с Cosmosdb, и я не хочу зависеть от всего DocumentDb SDK и нести за него дополнительную нагрузку.

И так как я на ненадежном клиенте, я использую resource tokens для аутентификации.Все разделено на части.

В целях тестирования я воспроизвел то, что пытался сделать, используя вызовы REST SQL и DocumentClient.

Я успешно получил один документ, выполнив вызов Get с использованием REST и токена ресурса.Это также отлично работает для подхода DocumentClient.

Пока все хорошо.

Когда я пытаюсь на самом деле сделать query, он прекрасно работает, используя DocumentClient и маркер ресурса.

Использование точно такого же запроса и того же маркера ресурса с вызовом REST приводит к Forbidden результату.

The permission mode provided in the authorization token doesn't provide sufficient permissions

Я где-то читал (и сейчас не могу его найти)что вам нужен мастер-токен для выполнения запросов с использованием вызовов REST.

Перед тем, как опубликовать и написать код, я испытываю ожидаемое поведение, или я действительно должен уметь запрашивать вызовы REST?

Заранее спасибо.

** ОБНОВЛЕНИЕ № 2 С ССЫЛКОЙ НА GITHUB REPO **

https://github.com/nhwilly/DocumentClientVsRest.git

ОБНОВЛЕНИЕ С ОБРАЗЦОМ КОДА

using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Newtonsoft.Json;

namespace DocClientVsRestCallTest
{
    /// <summary>
    /// The purpose of this console app is to determine why I can't get a REST call
    /// to work on a read only resource token for Azure CosmosDb.  A direct comparison
    /// using identical paths and tokens should work.  I have an issue for sure, but I
    /// can't find it.  :(
    ///
    /// To run this, you need to have already created a partitioned CosmosDb collection.
    /// </summary>
    class Program
    {
        public static string dbServicePath = $"https://[YOUR-COSMOS-ACCOUNT-NAME].documents.azure.com";
        public static string databaseId = "[YOUR-DATABASE-ID]";
        public static string collectionId = "[YOUR-COLLECTION-ID]";
        public static string datetime = DateTime.UtcNow.ToString("R");
        public static string version = "2018-06-18";
        public static string resourceId = $"dbs/{databaseId}/colls/{collectionId}";
        public static string urlPath = $"{dbServicePath}/{resourceId}/docs";
        public static string partitionKey = $"TestPartition";
        public static string documentId = $"TestDocumentId";
        public static string queryString = $"select * from c where c.id='{documentId}' and c.partitionId ='{partitionKey}'";
        public static string userId = $"TestUser";
        public static string permissionToken = string.Empty;

        // the master key is supplied to create permission tokens and simulate the server only.
        public static string masterKey = $"[YOUR-MASTER-KEY]";

        static void Main(string[] args)
        {
            Debug.WriteLine("Starting...");

            // let's make sure we get a readonly token for the user/partition in question.
            permissionToken =
                Task.Run(async () => await GetPermissionToken()).GetAwaiter().GetResult();


            QueryUsingSdk();

            Task.Run(async () => await QueryUsingRest()).ConfigureAwait(false);

            Task.Run(async ()=> await CleanUp()).ConfigureAwait(false);

            Console.WriteLine("finished...");
            Console.ReadKey();
        }

        static async Task QueryUsingRest()
        {
            Uri uri = new Uri(urlPath);
            HttpClient client = new HttpClient();

            var encodedToken =
                HttpUtility.UrlEncode(permissionToken);

            string partitionAsJsonArray =
                JsonConvert.SerializeObject(new[] { partitionKey });

            client.DefaultRequestHeaders.Add("x-ms-date", datetime);
            client.DefaultRequestHeaders.Add("x-ms-documentdb-isquery", "True");
            client.DefaultRequestHeaders.Add("x-ms-documentdb-query-enablecrosspartition", "False");
            client.DefaultRequestHeaders.Add("x-ms-documentdb-query-iscontinuationexpected", "False");
            client.DefaultRequestHeaders.Add("x-ms-documentdb-partitionkey", partitionAsJsonArray);
            client.DefaultRequestHeaders.Add("authorization", encodedToken);
            client.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
            client.DefaultRequestHeaders.Add("x-ms-version", version);
            client.DefaultRequestHeaders.Accept
                .Add(new MediaTypeWithQualityHeaderValue("application/json"));

            var content =
                new StringContent(JsonConvert.SerializeObject(new { query = queryString }), Encoding.UTF8, "application/query+json");

            HttpResponseMessage response =
               await client.PostAsync(urlPath, content).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                await DisplayErrorMessage(response).ConfigureAwait(false);
            }
            else
            {
                Debug.WriteLine($"Success {response.StatusCode}!");
                var jsonString =
                    await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            }

        }


        static void QueryUsingSdk()
        {

            var docClient =
                new DocumentClient(new Uri(dbServicePath), permissionToken);

            var feedOptions =
                new FeedOptions { PartitionKey = new PartitionKey(partitionKey) };

            var result =
                docClient
                    .CreateDocumentQuery(UriFactory.CreateDocumentCollectionUri(databaseId, collectionId), queryString,
                        feedOptions)
                    .ToList().First();


            Debug.WriteLine($"SDK result: {result}");
        }


        /// <summary>
        /// This method simulates what would happen on the server during an authenticated
        /// request.  The token (and other permission info) would be returned to the client.
        /// </summary>
        /// <returns></returns>
        static async Task<string> GetPermissionToken()
        {
            string token = string.Empty;
            try
            {
                var docClient =
                    new DocumentClient(new Uri(dbServicePath), masterKey);

                var userUri =
                        UriFactory.CreateUserUri(databaseId, userId);

                // delete the user if it exists...
                try
                {
                    await docClient.DeleteUserAsync(userUri).ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    Debug.WriteLine($"Delete user error: {e.Message}");

                }

                // create the user
                var dbUri =
                    UriFactory.CreateDatabaseUri(databaseId);

                await docClient.CreateUserAsync(dbUri, new User { Id = userId }).ConfigureAwait(false);

                // create the permission
                var link =
                    await docClient
                        .ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(databaseId, collectionId))
                        .ConfigureAwait(false);

                var resourceLink =
                    link.Resource.SelfLink;

                var permission =
                    new Permission
                    {
                        Id = partitionKey,
                        PermissionMode = PermissionMode.Read,
                        ResourceLink = resourceLink,
                        ResourcePartitionKey = new PartitionKey(partitionKey)
                    };


                await docClient.CreatePermissionAsync(userUri, permission).ConfigureAwait(false);

                // now create a document that should be returned when we do the query
                var doc = new { id = documentId, partitionId = partitionKey, message = "Sample document for testing" };
                try
                {
                    await docClient.DeleteDocumentAsync(UriFactory.CreateDocumentUri(databaseId, collectionId,
                        documentId), new RequestOptions { PartitionKey = new PartitionKey(partitionKey) }).ConfigureAwait(false);


                }
                catch (Exception e)
                {
                    Debug.WriteLine($"Test document not found to delete - this is normal.");
                }

                try
                {
                    var document = await docClient
                        .CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(databaseId, collectionId), doc)
                        .ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    Debug.WriteLine($"Create document message: {e.Message}");
                }

                // now read the permission back as it would happen on the server
                var result = await docClient.ReadPermissionFeedAsync(userUri).ConfigureAwait(false);
                if (result.Count > 0)
                {
                    token = result.First(c => c.Id == partitionKey).Token;
                }


            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Create and get permission failed: {ex.Message}");
            }

            if (string.IsNullOrEmpty(token))
            {
                Debug.WriteLine("Did not find token");
            }
            return token;
        }

        static async Task CleanUp()
        {
            var docClient =
                new DocumentClient(new Uri(dbServicePath), masterKey);

            var doc = new { id = documentId, partitionId = partitionKey, message = "Sample document for testing" };
            try
            {
                await docClient.DeleteDocumentAsync(UriFactory.CreateDocumentUri(databaseId, collectionId,
                    documentId), new RequestOptions { PartitionKey = new PartitionKey(partitionKey) }).ConfigureAwait(false);


            }
            catch (Exception e)
            {
                Debug.WriteLine($"Delete document message: {e.Message}");
            }

        }

        static async Task DisplayErrorMessage(HttpResponseMessage response)
        {
            var messageDefinition =
                new
                {
                    code = "",
                    message = ""
                };

            var jsonString =
                await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            var message =
                JsonConvert.DeserializeAnonymousType(jsonString, messageDefinition);

            Debug.WriteLine($"Failed with {response.StatusCode} : {message.message}");

        }

    }
}

1 Ответ

0 голосов
/ 07 декабря 2018

Я испытываю ожидаемое поведение или я действительно могу запрашивать запросы с использованием вызовов REST?

Да, вы можете запрашивать документы в вызовах отдыха с помощью маркера ресурса.Пожалуйста, обратитесь к моему примеру кода Java для отдыха, как показано ниже:

import org.apache.commons.codec.binary.Base64;
import org.json.JSONArray;
import org.json.JSONObject;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

public class QueryDocumentsRest {

    private static final String account = "***";

    private static final String query = "select * from c";

    public static void main(String args[]) throws Exception {

        String urlString = "https://" + account + ".documents.azure.com/dbs/db/colls/coll/docs";
        HttpURLConnection connection = (HttpURLConnection) (new URL(urlString)).openConnection();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("query",query);
        JSONArray jsonArray = new JSONArray();
        jsonObject.put("parameters",jsonArray);
        byte[] data = (jsonObject.toString()).getBytes("UTF-8");

        connection.setRequestMethod("POST");
        connection.setRequestProperty("x-ms-version", "2017-02-22");
        connection.setRequestProperty("x-ms-documentdb-isquery", "true");
        //connection.setRequestProperty("x-ms-documentdb-query-enablecrosspartition", "true");

        connection.setRequestProperty("Content-Type", "application/query+json");
        System.out.println(data.length);
        connection.setRequestProperty("Content-Length", String.valueOf(data.length));

        SimpleDateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss");
        fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = fmt.format(Calendar.getInstance().getTime()) + " GMT";
        String auth = getAuthenticationString();

        connection.setRequestProperty("x-ms-date", date);
        connection.setRequestProperty("Authorization", auth);

        connection.setDoOutput(true);
        OutputStream os = connection.getOutputStream();

        os.write(data);
        os.flush();
        os.close();

        System.out.println("Response message : " + connection.getResponseMessage());
        System.out.println("Response code : " + connection.getResponseCode());
        System.out.println(connection.getHeaderField("x-ms-request-charge"));


        BufferedReader br = null;
        if (connection.getResponseCode() != 200) {
            br = new BufferedReader(new InputStreamReader((connection.getErrorStream())));
        } else {
            br = new BufferedReader(new InputStreamReader((connection.getInputStream())));
        }
        System.out.println("Response body : " + br.readLine());
    }


    private static String getAuthenticationString() throws Exception {
        String auth = "type=resource&ver=1&sig=***";
        auth = URLEncoder.encode(auth);
        System.out.println("authString:" + auth);
        return auth;
    }

}

Я установил режим разрешений как All во время моего теста.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...