How to run Sharepoint Rest API from server side with elevated privileges? - rest

The Sharepoint Rest API uses a simple URL of the type http://mysite/_api/search/query?querytext='search_key' to return search results as an XML. When I run this directly in a browser, I see a valid XML response:
(1) Am I right in assuming the above response is generated using the current user's authorization?
(2) Can this URL be invoked from server side? I tried it in a web method (WCF web service), but received a 401 - Unauthorized:
public string GetSearchResults(string searchKey)
{
string webURL = SPContext.Current.Web.Url;
string searchURL = webURL + "/_api/search/query?querytext='" + searchKey + "'";
WebClient client = new WebClient();
string xmlResponse = client.DownloadString(searchURL); // throws 401
// parse xmlResponse and return appropriately
}
(3) What I really need is to be able to get the search results irrespective of the current user's access rights (the requirement is that users will see all search results, with an option to "request access" when needed).
I tried this in the above web method, but it still throws the same 401:
public string GetSearchResults(string searchKey)
{
string webURL = SPContext.Current.Web.Url;
string searchURL = webURL + "/_api/search/query?querytext='" + searchKey + "'";
string xmlResponse;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
WebClient client = new WebClient();
xmlResponse = client.DownloadString(searchURL); // still 401
});
// parse xmlResponse and return appropriately
}
What is the right way to invoke the Rest URL from server side? Specifically, from a web method? And how can it be run as super user?

In order to perform REST request, authenticate the request via WebClient.Credentials Property
On Premise (your scenario)
WebClient client = new WebClient();
client.Credentials = new NetworkCredential(userName,password,domain);
SharePoint Online
WebClient client = new WebClient();
client.Credentials = new SharePointOnlineCredentials(username,securedPassword);
client.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");

Search results are always security trimmed by SharePoint so to make this work, you'd need to run your query after specifying new credentials as mentioned by Vadim. This is almost certainly not a good idea. If you're running code server side already, don't use the REST interface, just query directly using the search API.

Related

How to call SSRS Rest-Api V1.0 with custom security implemented (NOT SOAP)

I have implemented the custom security on my reporting services 2016 and it displays the login page once the URL for reporting services is typed on browser URL bar (either reports or reportserver)
I am using the following code to pass the Credentials
when i use the code WITHOUT my security extension it works and looks like this
ICredentials _executionCredentials;
CredentialCache myCache = new CredentialCache();
Uri reportServerUri = new Uri(ReportServerUrl);
myCache.Add(new Uri(reportServerUri.GetLeftPart(UriPartial.Authority)),
"NTLM", new NetworkCredential(MyUserName, MyUserPassword));
_executionCredentials = myCache;
when i use the code WITH the security extension it doesnt work and looks like this
ICredentials _executionCredentials;
CredentialCache myCache = new CredentialCache();
Uri reportServerUri = new Uri(ReportServerUrl);
myCache.Add(new Uri(reportServerUri.GetLeftPart(UriPartial.Authority)),
"Basic", new NetworkCredential(MyUserName, MyUserPassword));
_executionCredentials = myCache;
and i get an Exception saying "The response to this POST request did not contain a 'location' header. That is not supported by this client." when i actually use this credentials
Is "basic" the wrong option ?
Have anyone done this ?
Update 1
Well it turns out that my SSRS is expecting an Authorisation cookie
which i am unable to pass (according to fiddler, there is no cookie)
HttpWebRequest request;
request = (HttpWebRequest)HttpWebRequest.Create("http://mylocalcomputerwithRS/Reports_SQL2016/api/v1.0");
CookieContainer cookieJar = new CookieContainer();
request.CookieContainer = cookieJar;
Cookie authCookie = new Cookie("sqlAuthCookie", "username:password");
authCookie.Domain = ".mydomain.mylocalcomputerwithRS";
if (authCookie != null)
request.CookieContainer.Add(authCookie);
request.Timeout = -1;
HttpWebResponse myHttpWebResponse = (HttpWebResponse)request.GetResponse();
That's how I got it (SSRS 2017; api v2.0). I took the value for the "body" from Fiddler:
var handler = new HttpClientHandler();
var httpClient = new HttpClient(handler);
Assert.AreEqual(0, handler.CookieContainer.Count);
// Create a login form
var body = new Dictionary<string, string>()
{
{"__VIEWSTATE", "9cZYKBmLKR3EbLhJvaf1JI7LZ4cc0244Hpcpzt/2MsDy+ccwNaw9hswvzwepb4InPxvrgR0FJ/TpZWbLZGNEIuD/dmmqy0qXNm5/6VMn9eV+SBbdAhSupsEhmbuTTrg7sjtRig==" },
{"__VIEWSTATEGENERATOR", "480DEEB3"},
{ "__EVENTVALIDATION", "IS0IRlkvSTMCa7SfuB/lrh9f5TpFSB2wpqBZGzpoT/aKGsI5zSjooNO9QvxIh+QIvcbPFDOqTD7R0VDOH8CWkX4T4Fs29e6IL92qPik3euu5QpidxJB14t/WSqBywIMEWXy6lfVTsTWAkkMJRX8DX7OwIhSWZAEbWZUyJRSpXZK5k74jl4x85OZJ19hyfE9qwatskQ=="},
{"txtUserName", "User"},
{"txtPassword", "1"},
{"btnLogin","Войти"}
};
var content = new FormUrlEncodedContent(body);
// POST to login form
var response = await httpClient.PostAsync("http://127.0.0.1:777/ReportServer/Logon.aspx", content);
// Check the cookies created by server
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var cookies = handler.CookieContainer.GetCookies(new Uri("http://127.0.0.1:777/ReportServer"));
Assert.AreEqual("sqlAuthCookie", cookies[0].Name);
// Make new request to secured resource
var myresponse = await httpClient.GetAsync("http://127.0.0.1:777/Reports/api/v2.0/Folders");
var stringContent = await myresponse.Content.ReadAsStringAsync();
Console.Write(stringContent);
As an alternative you can customize SSRS Custom Security Sample quite a bit.
I forked Microsoft's Custom Security Sample to do just what you are describing (needed the functionality at a client long ago and reimplemented as a shareable project on GitHub).
https://github.com/sonrai-LLC/ExtRSAuth
I created a YouTube walkthrough as well to show how one can extend and debug SSRS security with this ExtRSAuth SSRS security assembly https://www.youtube.com/watch?v=tnsWChwW7lA
TL; DR; just bypass the Microsoft example auth check in Login.aspx.cs and put your auth in Page_Load() or Page_Init() event of Login.aspx.cs- wherever you want to perform some custom logging check- and then immediately redirect auth'd user to their requested URI.

Adding API key to header for WCF service to check

I am implementing an api key for a basic web service I have. I am using an implementation found here: https://blogs.msdn.microsoft.com/rjacobs/2010/06/14/how-to-do-api-key-verification-for-rest-services-in-net-4/
I know I have it all implemented and setup correctly on the service side but I am not sure how to pass the API key from my client. When I debug the web service upon request I don't get anything returned for my HttpRequestMessage query string. Here is code:
Web service auth manager:
public string GetAPIKey(OperationContext oc)
{
// get the request
var request = oc.RequestContext.RequestMessage;
// get HTTP request message
var requestProp = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
// get the actual query string
NameValueCollection queryParams = HttpUtility.ParseQueryString(requestProp.QueryString);
// return APIKey if there, NameValueCollection returns null if not present
return queryParams[APIKEY];
}
Client consumption (the part that matters):
using (WebClient client = new WebClient())
{
client.Headers.Add("Content-Type", "application/json");
client.Headers.Add("APIKey","my_generated_key");
client.Encoding = Encoding.UTF8;
Console.WriteLine(client.UploadString("http://my_local_host/my.svc/myCall", "POST", data));
}
During debug, the web service is always getting empty queryParams in the NameValueCollection because the query string is empty. How do I add to that query string during the request made from the client?
Solved. The solution was to not try to pull from the HttpRequestMessageProprty.QueryString but to just pull from the headers.
Code:
public string GetAPIKey(OperationContext oc)
{
// get the request
var request = oc.RequestContext.RequestMessage;
// get HTTP request message
var requestProp = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
// get the actual query string
NameValueCollection queryParams = requestProp.Headers;
// return APIKey if there, NameValueCollection returns null if not present
return queryParams["APIKey"];
}

How to call Windows Azure Storage RestFul Service without SDK?

I try to use Windows Azure like a Storage fom Salesforce.com.
I cheked the documentation and I only can see call the calls to azure rest api from SDK (Java, .Net, JS, etc) examples.
I need integrate Salesforce with Windows Azure Storage but, Azure don't have a SDK for Salesforce.com
From Salesforce.com is allow the calls to rest services but the process to call Azure Rest Services require one o more librarys.
Exameple:
Authentication for the Azure Storage Services require of:
Headers: Date Header and Authorization Header
The Authorization Header require two elments
SharedKey
Account Name
Authorization="[SharedKey|SharedKeyLite] :"
SharedKey and Account Name give a conversion:
HMAC-SHA256 conversion
over UTF-8 encoded
For this convertion the documentation referes to SDK Librarys in others words Java Class or .Net Class type helper that in Salesforce.com not exist.
Please, I need a example to call the authentification service without sdk
Sorry for my bad English.
Visit: https://learn.microsoft.com/en-us/rest/api/storageservices/fileservices/authentication-for-the-azure-storage-services
I need a example to call the authentification service without sdk
We could generate signature string and specify Authorization header for the request of performing Azure storage services without installing SDK. Here is a simple working sample to list the containers, you could refer to my generateAuthorizationHeader function and Authentication for the Azure Storage Services to construct the signature string.
string StorageAccount = "mystorageaccount";
string StorageKey = "my storage key";
string requestMethod = "GET";
string mxdate = "";
string storageServiceVersion = "2014-02-14";
protected void btnlist_Click(object sender, EventArgs e)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(string.Format(CultureInfo.InvariantCulture,
"https://{0}.blob.core.windows.net/?comp=list",
StorageAccount
));
req.Method = requestMethod;
//specify request header
string AuthorizationHeader = generateAuthorizationHeader();
req.Headers.Add("Authorization", AuthorizationHeader);
req.Headers.Add("x-ms-date", mxdate);
req.Headers.Add("x-ms-version", storageServiceVersion);
using (HttpWebResponse response = (HttpWebResponse)req.GetResponse())
{
var stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream);
string content = reader.ReadToEnd();
StringReader theReader = new StringReader(content);
DataSet theDataSet = new DataSet();
theDataSet.ReadXml(theReader);
DataTable dt = theDataSet.Tables[2];
}
}
public string generateAuthorizationHeader()
{
mxdate = DateTime.UtcNow.ToString("R");
string canonicalizedHeaders = string.Format(
"x-ms-date:{0}\nx-ms-version:{1}",
mxdate,
storageServiceVersion);
string canonicalizedResource = string.Format("/{0}/\ncomp:list", StorageAccount);
string stringToSign = string.Format(
"{0}\n\n\n\n\n\n\n\n\n\n\n\n{1}\n{2}",
requestMethod,
canonicalizedHeaders,
canonicalizedResource);
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(StorageKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
String authorization = String.Format("{0} {1}:{2}",
"SharedKey",
StorageAccount,
signature
);
return authorization;
}
Besides, please refer to Azure Storage Services REST API Reference to know more about programmatic access to Azure Storage Services via REST APIs.
I find a way to solve this.
You should use Shared Sing, here explain me:
Enter to Portal Azure
Open the Account Storage
In the General Information click on "Share sing access"
Enable all permissions that you need (In my case only Enable "File")
Enable all resources permission that you need (In my case onl Enable "Service, Container and Object")
Define and Start Date and End Date (This is the space of time that Shared Key will be valid)
Define protocol type (In my case use HTTPS)
Clic on "Generate SAS" button
After this process you will get a token like this:
?sv=2016-05-31&ss=f&srt=sco&sp=rwdlc&se=2017-11-28T04:29:49Z&st=2017-02-18T20:29:49Z&spr=https&sig=rt7Loxo1MHGJqp0F6ryLhYAmOdRreyiYT418ybDN2OI%3D
You have to use this Token like Autentication
Example Call Code List a Content:
public with sharing class CallAzureRestDemo {
public string token = '&sv=2016-05-31&ss=f&srt=sco&sp=rwdlc&se=2017-02-19T04:00:44Z&st=2017-02-18T20:00:44Z&spr=https&sig=GTWGQc5GOAvQ0BIMxMbwUpgag5AmUVjrfZc56nHkhjI%3D';
//public Integer batchSize;
public CallAzureRestDemo(){}
public void getlistcontent(String endpoint)
{
// Create HTTP GET request
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(endpoint+token);
Http http = new Http();
HTTPResponse res;
System.debug(LoggingLevel.INFO, '##RESPONSE: '+res);
// only do this if not running in a test method
if(!Test.isRunningTest())
{
System.debug(LoggingLevel.INFO, 'Sending the message to Azure');
res = http.send(req);
System.debug(LoggingLevel.INFO, 'http.send result status: ' + res.getStatus());
}
else
{
System.debug(LoggingLevel.INFO, 'Running in a test so not sending the message to Azure');
}
}
}
Example TestMethod:
#isTest
private class Test_CallAzureRestDemo {
static testMethod void myUnitTest() {
CallAzureRestDemo oRest = new CallAzureRestDemo();
try{
//Call the method and set endpoint
oRest.getlistcontent('https://accountstoragecomex.file.core.windows.net/?comp=list');
}catch(Exception e){
System.debug('##'+e);
}
}
}
Example to Response:
20:15:47.64 (79388244)|CALLOUT_REQUEST|[100]|System.HttpRequest[Endpoint=https://accountstoragecomex.file.core.windows.net/?comp=list&sv=2016-05-31&ss=f&srt=sco&sp=rwdlc&se=2017-02-19T04:00:44Z&st=2017-02-18T20:00:44Z&spr=https&sig=GTWGQc5GOAvQ0BIMxMbwUpgag5AmUVjrfZc56nHkhjI%3D, Method=GET]
20:15:47.64 (395755012)|CALLOUT_RESPONSE|[100]|System.HttpResponse[Status=OK, StatusCode=200]
Example Call Service "FILE - Get List Share"
Call To List Content
One more time, Sorry for my bad english.

Changing user folder collaborating type in box using Salesforce Toolbox

I'm trying to change Box folder collaboration type for user from salesforce Apex trigger. The first thoughts were to use box.Toolkit but it looks like this class does not have updateCollaboration or changeCollaboration method, only create. I guess my only option is to use Box's Rest API. Is there any way I can get service account token in Apex so I can use it in a callout?
I have created a special "Tokens" object in Salesforce with two fields: access token and refresh token. I then have a batch job that runs to update the access token every 55 minutes such that they never expired.
Here is a code snippet in APEX using the Tokens object.
#future(callout=true)
public static void updateTokens(){
//app info for authenticating
String clientID = 'MY_CLIENT_ID';
String clientSecret = 'MY_CLIENT_SECRET';
//look up value of existing refresh token
Token__c myToken = [SELECT Name, Value__c FROM Token__c WHERE Name='Refresh'];
Token__c myAccessToken = [SELECT Name, Value__c FROM Token__c WHERE Name='Access'];
String refreshToken = myToken.Value__c;
String accessToken = myAccessToken.Value__c;
//variables for storing data
String BoxJSON = '';
String debugTxt = '';
//callout to Box API to get new tokens
HttpRequest reqRefresh = new HttpRequest();
reqRefresh.setMethod('POST');
String endpointRefresh = 'https://www.box.com/api/oauth2/token';
reqRefresh.setEndpoint(endpointRefresh);
String requestBody = ('grant_type=refresh_token&refresh_token=' + refreshToken + '&client_id=' + clientID + '&client_secret=' + clientSecret);
reqRefresh.setBody(requestBody);
System.debug('Body of refresh request: ' + requestBody);
//Create Http, send request
Http httpRefresh = new Http();
Boolean successRefresh = false;
while (successRefresh == false){
try{
HTTPResponse resRefresh = httpRefresh.send(reqRefresh);
BoxJSON = resRefresh.getBody();
System.debug('Body of refresh response: ' + BoxJSON);
successRefresh = true;
}
catch (System.Exception e){
System.debug('Error refreshing: ' + string.valueof(e));
if (Test.isRunningTest()){
successRefresh = true;
}
}
}
Keep in mind that if you are using the Box for Salesforce integration your administrator can set the option for the permissions on the folders to sync with Salesforce permissions. This would reverse any changes you make to collaborations. Check out more about Box's Salesforce integration permissions here: https://support.box.com/hc/en-us/articles/202509066-Box-for-Salesforce-Administration#BfS_admin_perm

Must/can I install MS ASP.NET Web API Client Libraries in order to post data to my Web API server?

Do I need to install ASP.NET Web API Client Libraries (as this article indicates) in order to post data to a Web API server? If so, can I do so in Visual Studio 2008 from a Windows CE project?
The reasons I wonder are:
0) The client is a Windows CE project, for which I'm using Visual Studio 2008, and I don't know if ASP.NET Web API Client Libraries are available for that version; I know I don't have the NuGet Package Manager in that environment.
1) I am successfully querying data from my RESTful Web API methods without installing ASP.NET Web API Client Libraries, using code like this:
while (true)
{
deptList.departments.Clear();
string uri = String.Format("http://platypi:28642/api/Duckbills/{0}/{1}", lastIdFetched, RECORDS_TO_FETCH);
var webRequest = (HttpWebRequest) WebRequest.Create(uri);
webRequest.Method = "GET";
using (var webResponse = (HttpWebResponse)webRequest.GetResponse())
{
if (webResponse.StatusCode == HttpStatusCode.OK)
{
var reader = new StreamReader(webResponse.GetResponseStream());
string jsonizedDuckbills = reader.ReadToEnd();
List<Duckbill> duckbills = JsonConvert.DeserializeObject<List<Duckbill>>(jsonizedDuckbills);
if (duckbills.Count <= 0) break;
foreach (Duckbill duckbill in duckbills)
{
duckbillList.duckbills.Add(duckbill);
lastIdFetched = duckbill.Id;
}
} // if ((webResponse.StatusCode == HttpStatusCode.OK)
} // using HttpWebResponse
int recordsAdded = LocalDBUtils.BulkInsertDuckbills(duckbillList.duckbills);
totalRecordsAdded += recordsAdded;
} // while (true);
I'm stuck on posting, though, and the cleanest example I've seen so far for doing so is at that link already shown above.
I got an answer to my question on how to post here, but that hasn't made me smart enough yet to actually accomplish it. It's a step in the right direction, perhaps, although I reckon, based on how my client query code looks, that the client posting code would be of similar "style" (like the previously referenced article here, and unlike the likewise previously referenced answer here).
UPDATE
If I'm already providing the data in the uri string itself, as I am, like this:
string uri = String.Format("http://shannon2:28642/api/Departments/{0}/{1}", onAccountOfWally, moniker);
...why would I need to also specify it in postData? Or could I set postData (if that's just a necessary step to get the length) to those values...something like:
postData = String.Format("{0}, {1}", onAccountOfWally, moniker);
?
To talk to ASP.NET Web API, you do not necessarily need the client library, although it makes the life easier. After all, one of the benefits of HTTP services is the platform reach. Literally you can use any library that gives you HTTP capabilities. So, using WebRequest, you can do something like this. I'm using JSON in the payload. You can use XML and application/www-form-urlencoded as well. Just that you need to format the request body accordingly. Also, for complex objects, you will be better off using JSON.NET unlike formatting the JSON manually.
var request = System.Net.WebRequest.Create("http://localhost:12345/api/values");
request.Method = "POST";
string postData = "{\"firstName\":\"Steven\"," + "\"lastName\":\"Waugh\"}";
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
request.ContentType = "application/json";
request.ContentLength = byteArray.Length;
using (var requestStream = request.GetRequestStream())
{
requestStream.Write(byteArray, 0, byteArray.Length);
}
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
using (var reader = new System.IO.StreamReader(responseStream))
{
string responseFromServer = reader.ReadToEnd();
Console.WriteLine(responseFromServer);
}
}
}
EDIT
If you are specifying data in URI, you do not need to specify the same in the request body. To let web API bind the parameters for you from URI, you will need to specify the route accordingly so that the placeholders are set for onAccountOfWally and moniker. Then you will need to use a simple type like string as action method parameters for web API to bind. By default, simple types are bound from URI path and query string and complex types from request body.