VersionOne: query.v1 C# OAuth2 gets 401 Unauthorized error but rest-1.oauth.v1/Data/ does work

828 Views Asked by At

I am able to do queries using OAuth2 and this:

/rest-1.oauth.v1/Data/Story?sel=Name,Number&Accept=text/json

However I am unable to get the OAuth2 and the new query1.v1 to work against the Sumnmer2013 VersionOne. I am getting (401) Unauthorized and specified method is not supported using two different URLs.

Here is the code that includes the working /rest-1.oauth.v1 and the non-working query1.v1 and non-working query.legacy.v1. Scroll towards the bottom of the code to see the Program Main (starting point of code) Please advise on what I am missing here.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;  
using System.Text;
using System.Threading.Tasks;
using OAuth2Client;

namespace ExampleMemberListCSharp
{
    class Defaults
    {
        public static string Scope = "apiv1";
        //public static string EndpointUrl = "http://localhost/VersionOne.Web";
        public static string EndpointUrl = "https://versionone-test.web.acme.com/summer13_demo";
        public static string ApiQueryWorks = "/rest-1.oauth.v1/Data/Member?Accept=text/json";
        public static string ApiQuery = "/rest-1.oauth.v1/Data/Story?sel=Name,Number&Accept=text/json";
    }


    static class WebClientExtensions
    {
        public static string DownloadStringOAuth2(this WebClient client, IStorage storage, string scope, string path)
        {
            var creds = storage.GetCredentials();
            client.AddBearer(creds);
            try
            {
                return client.DownloadString(path);
            }
            catch (WebException ex)
            {
                if (ex.Status == WebExceptionStatus.ProtocolError)
                {
                    if (((HttpWebResponse)ex.Response).StatusCode != HttpStatusCode.Unauthorized)
                        throw;
                    var secrets = storage.GetSecrets();
                    var authclient = new AuthClient(secrets, scope);
                    var newcreds = authclient.refreshAuthCode(creds);
                    var storedcreds = storage.StoreCredentials(newcreds);
                    client.AddBearer(storedcreds);
                    return client.DownloadString(path);
                }
                throw;
            }
        }
        public static string UploadStringOAuth2(this WebClient client, IStorage storage
            , string scope, string path, string pinMethod, string pinQueryBody)
        {
            var creds = storage.GetCredentials();
            client.AddBearer(creds);
            client.UseDefaultCredentials = true;
            try
            {
                return client.UploadString(path, pinMethod, pinQueryBody);
            }
            catch (WebException ex)
            {
                if (ex.Status == WebExceptionStatus.ProtocolError)
                {
                    if (((HttpWebResponse)ex.Response).StatusCode != HttpStatusCode.Unauthorized)
                        throw;
                    var secrets = storage.GetSecrets();
                    var authclient = new AuthClient(secrets, scope);
                    var newcreds = authclient.refreshAuthCode(creds);
                    var storedcreds = storage.StoreCredentials(newcreds);
                    client.AddBearer(storedcreds);
                    client.UseDefaultCredentials = true;
                    return client.UploadString(path, pinMethod, pinQueryBody);
                }
                throw;
            }
        }
    }


    class AsyncProgram
    {
        private static async Task<string> DoRequestAsync(string path)
        {
            var httpclient = HttpClientFactory.WithOAuth2("apiv1");

            var response = await httpclient.GetAsync(Defaults.EndpointUrl + Defaults.ApiQuery);
            var body = await response.Content.ReadAsStringAsync();
            return body;
        }

        public static int MainAsync(string[] args)
        {
            var t = DoRequestAsync(Defaults.EndpointUrl + Defaults.ApiQuery);
            Task.WaitAll(t);
            Console.WriteLine(t.Result);
            return 0;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            IStorage storage = Storage.JsonFileStorage.Default;
            using (var webclient = new WebClient())
            {
                // this works:
                var body = webclient.DownloadStringOAuth2(storage, "apiv1", Defaults.EndpointUrl + Defaults.ApiQuery);
                Console.WriteLine(body);
            }
            IStorage storage2 = Storage.JsonFileStorage.Default;

            using (var webclient2 = new WebClient())
            {
                // This does NOT work.  It throws an exception of  (401) Unauthorized:
                 var body2 = webclient2.UploadStringOAuth2(storage2, "apiv1", Defaults.EndpointUrl + "/query.v1", "SEARCH", QueryBody);
                // This does NOT work. It throws an exception of The remote server returned an error: (403): Forbidden."
                var body3 = webclient2.UploadStringOAuth2(storage2, "apiv1", Defaults.EndpointUrl + "/query.legacy.v1", "SEARCH", QueryBody);

                // These do NOT work.  Specified method is not supported:
                var body4 = webclient2.UploadStringOAuth2(storage2, "apiv1", Defaults.EndpointUrl + "/oauth.v1/query.legacy.v1", "SEARCH", QueryBody);
                var body5 = webclient2.UploadStringOAuth2(storage2, "apiv1", Defaults.EndpointUrl + "/oauth.v1/query.legacy.v1", "SEARCH", QueryBody);


            }
            Console.ReadLine();
            AsyncProgram.MainAsync(args);
        }

        public const string QueryBody = @"

from: Story
select:
- Name
";
    }
}
1

There are 1 best solutions below

1
On

At this time, the query.v1 endpoint requires the query-api-1.0 scope to be granted.

You'll have to add that to your scope list (it can be simply space-separated e.g. apiv1 query-api-1.0) and visit the grant URL again to authorize the permissions.

This somewhat vital piece of information doesn't seem to appear in the docs on community.versionone.com, so it looks like an update is in order.

Also, only the rest-1.oauth.v1 and query.v1 endpoints respond to OAuth2 headers at this time. A future release will see it apply to all endpoints and remove the endpoint duplication for the two types of authentication

I have had problems in the past trying to use an HTTP method other than POST to transmit the query. Security software, IIS settings, and proxies may all handle such requests in unexpected ways.