A web service available without authorization + CORS

Question

How can I call a web service created in one bpm'online from another bpm'online? I get the "Cross-origin resource sharing" error.

Answer

The below info has been tested on our test platforms. The lead column used for the search was named with a prefix: UsrTest. The classes utils and the service were named with prefixes: UsrMyTestService, UsrLeadSearchUtils.

As a result, we have:

The service schema code

amespace Terrasoft.Configuration.CustomConfigurationService
{
	using System;
	using System.Collections.Generic;
	using System.Runtime.Serialization;
	using System.Linq;
	using System.ServiceModel;
	using System.ServiceModel.Web;
	using System.ServiceModel.Activation;
	using System.Web;
	using System.IO;
	using Terrasoft.Core;
	using Terrasoft.Core.Configuration;
	using Terrasoft.Core.DB;
	using Terrasoft.Core.Entities;
 
	[ServiceContract]
	[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
	public class UsrMyTestService
	{
		private UserConnection _userConnection;
 
		private UserConnection UserConnection {
			get {
				return _userConnection ?? (_userConnection = HttpContext.Current.Session["UserConnection"] as UserConnection);
			}
		}
 
		[OperationContract]
		[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped,
			ResponseFormat = WebMessageFormat.Json)]
		public testclass myTestServiceFunction(string test)
		{
			//////
			var currentWebOperationContext = WebOperationContext.Current;
			var outgoingResponseHeaders = currentWebOperationContext.OutgoingResponse.Headers;
			var incomingRequestOrigin = currentWebOperationContext.IncomingRequest.Headers["Origin"];
			outgoingResponseHeaders.Add("Access-Control-Allow-Origin", incomingRequestOrigin);
			///////
			testclass result = new testclass();
			UsrLeadSearchUtils leadSearchUtil = new UsrLeadSearchUtils(this.UserConnection);
			try {
				var leadCollection = leadSearchUtil.SearchLeadByCustomerID(test);
				foreach (var item in leadCollection) {
					result.Response.Add(new SearchItem() {
						Id = item.GetTypedColumnValue<Guid>("LeadId"),
						Name = item.GetTypedColumnValue<string>("LeadName"),
					});
				}
			} catch (Exception ex) {
				result.Success = false;
				result.ErrorMessage = ex.Message;
			};
			return result;
		}
 
		////////
		[OperationContract]
		[WebInvoke(Method = "OPTIONS", UriTemplate = "SaveWebFormLeadData")]
		public void GetWebFormLeadDataRequestOptions() {
			var outgoingResponseHeaders = WebOperationContext.Current.OutgoingResponse.Headers;
			outgoingResponseHeaders.Add("Access-Control-Allow-Origin", "*");
			outgoingResponseHeaders.Add("Access-Control-Allow-Methods", "POST");
			outgoingResponseHeaders.Add("Access-Control-Allow-Headers", "Origin, Content-Type, Accept");
		}
		//////////
 
		[DataContract]
		public class testclass {
			public testclass() {
				this.Success = true;
				this.Response = new List<SearchItem>();
			}
			[DataMember]
			public List<SearchItem> Response {
				get;
				set;
			}
			[DataMember]
			public bool Success {
				get;
				set;
			}
			[DataMember]
			public string ErrorMessage {
				get;
				set;
			}
		}
 
		[DataContract]
		public class SearchItem {
			[DataMember]
			public Guid Id {
				get;
				set;
			}
			[DataMember]
			public string Name {
				get;
				set;
			}
		}
	}
}

The utility schem code

namespace Terrasoft.Configuration
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using Terrasoft.Core;
	using Terrasoft.Core.Configuration;
	using Terrasoft.Core.DB;
	using Terrasoft.Core.Entities;
 
	public class UsrLeadSearchUtils {
		public UsrLeadSearchUtils (UserConnection userConnection) {
			this.UserConnection = userConnection;
		}
 
		private UserConnection UserConnection {
			get;
			set;
		}
 
		public EntityCollection SearchLeadByCustomerID(string email) {
			if (string.IsNullOrEmpty(email)) {
				throw new ArgumentException("email is undefined.");
			}
 
			var entitySchemaManager = this.UserConnection.GetSchemaManager("EntitySchemaManager") as EntitySchemaManager;
			var esq = new EntitySchemaQuery(entitySchemaManager, "Lead");
			esq.RowCount = 20;
			esq.AddColumn("Id").Name = "LeadId";
			esq.AddColumn("LeadName").Name = "LeadName";
			esq.AddColumn("UsrTest").Name = "UsrTest";
			esq.Filters.Add(esq.CreateFilterWithParameters(FilterComparisonType.Equal, "UsrTest", 
				email));
 
			return esq.GetEntityCollection(this.UserConnection);
		}
	}
}

To call the web service from bpm'online where the service was created, try the following:

onGetServiceInfoClick: function() {
    var usrTest = "test1";
    var serviceData = {
        test: usrTest
    };
    ServiceHelper.callService("UsrMyTestService", "myTestServiceFunction",
        function(response) {
            var result = response.myTestServiceFunctionResult;
            alert(result.Response[0].Name);
        }, serviceData, this
    );
}

We receive the search results. The name of the first lead found via the search line is displayed by the alert.

Now let us try to request the web service from another bpm'online. For this, our service must be registered as the one available without authorization, which involves changing the service code and the website config where the service is located.

Working with the config in more detail:

1) Modify UsrMyTestService, locate it into the "Terrasoft.Configuration" namespace, add "UriTemplate" to the method and options. Establish inheritance from "BaseService". Use "SystemUserConnection" instead of usual connection, since we do not have authorization. The result is displayed below:

namespace Terrasoft.Configuration
{
	using System;
	using System.Collections.Generic;
	using System.Runtime.Serialization;
	using System.Linq;
	using System.ServiceModel;
	using System.ServiceModel.Web;
	using System.ServiceModel.Activation;
	using System.Web;
	using System.IO;
	using Terrasoft.Core;
	using Terrasoft.Core.Configuration;
	using Terrasoft.Core.DB;
	using Terrasoft.Core.Entities;
	using Terrasoft.Web.Common;
 
	[ServiceContract]
	[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
	public class UsrMyTestService : BaseService
	{
		private SystemUserConnection _systemUserConnection;
		private SystemUserConnection SystemUserConnection {
			get {
				return _systemUserConnection ?? (_systemUserConnection = (SystemUserConnection)AppConnection.SystemUserConnection);
			}
		}
 
		[OperationContract]
		[WebInvoke(Method = "GET", RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped,
		ResponseFormat = WebMessageFormat.Json, UriTemplate = "myTestServiceFunction/{test}/")]
		public testclass myTestServiceFunction(string test)
		{
			///////
			var currentWebOperationContext = WebOperationContext.Current;
			var outgoingResponseHeaders = currentWebOperationContext.OutgoingResponse.Headers;
			var incomingRequestOrigin = currentWebOperationContext.IncomingRequest.Headers["Origin"];
			outgoingResponseHeaders.Add("Access-Control-Allow-Origin", incomingRequestOrigin);
			///////
			testclass result = new testclass();
			try {
				UsrLeadSearchUtils leadSearchUtil = new UsrLeadSearchUtils(this.SystemUserConnection);
				var leadCollection = leadSearchUtil.SearchLeadByCustomerID(test);
				foreach (var item in leadCollection) {
					result.Response.Add(new SearchItem() {
						Id = item.GetTypedColumnValue<Guid>("LeadId"),
						Name = item.GetTypedColumnValue<string>("LeadName"),
					});
				}
			} catch (Exception ex) {
				result.Success = false;
				result.ErrorMessage = ex.Message;
			};
			return result;
		}
 
		[OperationContract]
		[WebInvoke(Method = "OPTIONS", UriTemplate = "*")]
		public void GetWebFormLeadDataRequestOptions() {
			var outgoingResponseHeaders = WebOperationContext.Current.OutgoingResponse.Headers;
			outgoingResponseHeaders.Add("Access-Control-Allow-Origin", "*");
			outgoingResponseHeaders.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
			outgoingResponseHeaders.Add("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, X-Requested-With, X-Requested-With, x-request-source");
			outgoingResponseHeaders.Add("Access-Control-Request-Headers", "X-Requested-With, x-request-source, accept, content-type");
		}
 
		[DataContract]
		public class testclass {
			public testclass() {
				this.Success = true;
				this.Response = new List<SearchItem>();
			}
			[DataMember]
			public List<SearchItem> Response {
				get;
				set;
			}
			[DataMember]
			public bool Success {
				get;
				set;
			}
			[DataMember]
			public string ErrorMessage {
				get;
				set;
			}
		}
 
		[DataContract]
		public class SearchItem {
			[DataMember]
			public Guid Id {
				get;
				set;
			}
			[DataMember]
			public string Name {
				get;
				set;
			}
		}
	}
}

The utility class remains unchanged.

2) In the folder of the website where our service is registered *website*\Terrasoft.WebApp\ServiceModel, create the "UsrMyTestService.svc" file.

The file should contain:

<%@ ServiceHost Language="C#" Debug="true" Service="Terrasoft.Configuration.UsrMyTestService" CodeBehind="UsrMyTestService.svc.cs" %>

3) In the *website* \Terrasoft.WebApp folder, in the Web.config file, along with other locations, add:

<location path="ServiceModel/UsrMyTestService.svc">
   <system.web>
      <authorization>
         <allow users="*" />
      </authorization>
   </system.web>
</location>

4) In the *website*\Terrasoft.WebApp\ folder, in the Web.config file, change the AllowedLocations key value. Add the following to the value:

ServiceModel/UsrMyTestService.svc;

5) In the *website*\Terrasoft.WebApp\ServiceModel\http folder, in the services.config file, add the following to the service block:

<service name="Terrasoft.Configuration.UsrMyTestService">
   <endpoint name="UsrMyTestServiceEndPoint"
      address=""
      binding="webHttpBinding"
      behaviorConfiguration="RestServiceBehavior"
      bindingNamespace="http://Terrasoft.WebApp.ServiceModel"
      contract="Terrasoft.Configuration.UsrMyTestService" />
</service>

6) in the *website*\Terrasoft.WebApp\ServiceModel\https folder, in the services.configfile, repeat actions fro mp.5 in the service block.

The service will be available at:

http(или https)://адрес-сайта/0/ServiceModel/UsrMyTestService.svc

As part of the test, all the above actions were performed at our local website:

http://localhost:8012/

To address this service from another website located on our local platforms at http://localhost:8006/, let us develop the contact schema as follows:

define("ContactPageV2", ["ContactPageV2Resources", "GeneralDetails", "JQuery"],
function(resources, GeneralDetails) {
	return {
		entitySchemaName: "Contact",
		details: /**SCHEMA_DETAILS*/{}/**SCHEMA_DETAILS*/,
		diff: /**SCHEMA_DIFF*/[
		]/**SCHEMA_DIFF*/,
		attributes: {},
		methods: {
 
			onEntityInitialized: function() {
				this.callParent(arguments);
				document.scp = this;
			},
 
			test: function() {
				var params = {
					test: "test1"
				};
				require(["jQuery"], function() {
					$.ajax({
						url: "http://localhost:8012/0/ServiceModel/UsrMyTestService.svc/myTestServiceFunction/" +
							params.test + "/",
						success: function(data) {
							alert(JSON.stringify(data));
						},
						error: function() {
							alert("Error occured!");
						}
					});
				});
			}
		},
		rules: {},
		userCode: {}
	};
});

As a result, we have a call method of a third-party web service from a third-party domain (different ports are considered different domains) and a scope cahed directly in the document (it is done only for testing purposes, you will not have to do it in your work). You can now try to call this method directly from the browser console and see the result:

 

 

Like 1

Like

Share

2 comments

After update CORS on Feb2020 there is next problem: "A cookie associated with a cross-site resource at (..,site on bpm...) was set without the `SameSite` attribute. It has been blocked, as Chrome now only delivers cookies with cross-site requests if they are set with `SameSite=None` and `Secure`.".

 

Where I can change cookies? How to fix it?

Hello Nataliia, 

This issue happens only with HTTP on Chrome version 81 or older. Because SameSite function there is disabled by default. 
You can use either Chrome of the latest version or use the HTTPS protocol where this issue doesn't occurs at all. 

Kind regards,
Roman

Show all comments