Using Windows Credentials to instantiate OrganizationServiceProxy in CRM 2011 - silverlight

Has anybody tried to use the Windows credentials to create an instance of OrganizationServiceProxy in CRM 2011 (On-Premise) ? I have a WCF service that uses
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows"/>
</security>
and I am able to confirm that the user is authenticated (OperationContext.Current.ServiceSecurityContext.WindowsIdentity.IsAuthenticated) but I don't know how to generate/pass the ClientCredentials to create an instance of the CRM service. This is called from a Silverlight application that does not live in an IFrame inside CRM.
Thanks.

What you need to use separate user account to log into the OrganizationServiceProxy.
You wont be able retrieve the windows credentials to pass to the proxy for authentication.
The user that you do use needs prvActOnBehalfOfAnotherUser privilege associated with it.
Once this is done and you can successfullly login and retrieve a valid OrganizationServiceProxy, what you need to do as a consumer of the service is specify the CallerId whenever you are calling operations on it. This token you should retrieve from the xrm model using Xrm.Page.context.getUserId. See. http://msdn.microsoft.com/en-us/library/gg334511.aspx.
Then from silverlight you would use the System.Windows.Browser.ScriptObject bridge to execute client side javascript to retrieve the userid of current user logged into crm.
Preferably do this at application bootstrap time and save the value into an applicationdata variable so so can access it globally from within your silverlight app.
Eg. of client side script.
function CrmContext() {
}
var context = null;
with (window.parent) {
context = Xrm.Page.context;}
CrmContext.prototype.ReadUserId = function () {
var userId = context.getUserId();
return userId;
}
Once you have the user token set the Proxy CallerId with this value
Eg.
private OrganizationServiceProxy Proxy { get; set; }
public Guid Create(CreateEntity request)
{
if (request == null || request.UserId == Guid.Empty || request.Entity == null)
{
throw new InvalidMessageException("Invalid reqest message. Please provide compulsory criteria");
}
var result = Guid.Empty;
try
{
if (Proxy != null)
{
Proxy.CallerId = request.UserId;
using (Proxy)
{
result = Proxy.Create(request.Entity);
}
}
}
catch (FaultException<OrganizationServiceFault> e)
{
Log.Error(e.Message);
throw new IntegrationException(e.Message);
}
return result;
}
The approach ive taken to solve this was to create a crm adapter encapsulating the crm proxy and sending request object to service interface that includes the user token.
public OrganizationServiceAdapter(ICrmConfigurationContext crmConfigurationConext)
{
try
{
Proxy = new OrganizationServiceProxy(
crmConfigurationConext.OrganizationServiceConfiguration,
crmConfigurationConext.Credentials);
}
catch (Exception e)
{
//// TODO: Add local proxy pattern implementation for failover
Proxy = null;
Log.Error(e.Message);
throw new IntegrationException(ExceptionMessages.CouldNotLoginToOrganizationService());
}
}

Related

How do i manage the SQL Server connections in asp.net core MVC mode?

I am working on a APP's server program which using asp.net core MVC mode. And in the XXXXcontrller i never use code like "Connection.open()" and "Connection.close()",a HTTP GET action is like this.
public async Task<IActionResult> register(string phoneNum, string password, string userType)
{
//从数据库里找到m.Id和 id相同的车,赋给dbCars
var dbCars = await _context.dbUsers.SingleOrDefaultAsync(m => m.UserName == phoneNum);
// var dbCars= _context.dbCars.Where(s => s.Id == id).FirstOrDefault<dbCars>();
if (dbCars == null)//如果为空,则注册
{
//注册新用户
dbCars = new dbUsers();
dbCars.UserName = phoneNum;
dbCars.Password = password;
dbCars.UserType = userType;
dbCars.IsVerified = true;
_context.Add(dbCars);
await _context.SaveChangesAsync();
//注册完成之后,把ID、用户类型和是否认证返回回去
List<string> userInfo = new List<string>();
userInfo.Add(dbCars.Id.ToString().Trim());
userInfo.Add(dbCars.UserType.ToString().Trim());
userInfo.Add(dbCars.IsVerified.ToString());
string json = JsonConvert.SerializeObject(userInfo);
return Ok(json);
}
else //如果不为空就代表注册过了
{
return Ok("0"); //返回1代表注册过了
}
}
I wondered how should i maintain the SQL Connections when many users access this service?? Should I close these connections manually? Or it can be done automatically by some magic?? If many users access this service, do my program breaked down??
I am confused, who can give me an answer??
if you are using Entity Framework or ORM you should see and set the connection string on the web.config or if it's on a separate layer you can see the settings on the app.config. If you run the application it will use the startup project of your application connectionstring app.config(windows application) or web.config (web application)

Specflow-Share browser session between features if triggered between steps

I have implemented Specflow to reuse some steps across features as in this example -Specflow,Selenium-Share data between different Step definitions or classes .Since, in our project, we are integrating multiple features & reusing them. What is the best way to share browser session across features if its triggered in between steps as per the above approach?
My Scenario:
Once an application created, I need to launch new session, login different User-set different services and approve it.
But after logging in fails with below error on Step definition 4 in reused Whenstep of Given(Set the service to (.*)). That particular step is from different feature, hence the new session needs to be used in those steps. The LaunchURl method below is just launching the website with url, no new session created - This works fine
OpenQA.Selenium.WebDriverException : Unexpected error. System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it "IP here"
[Given(#"A New Application is added")]
public void GivenANewApplicationIsAdded()
{
Given("UK_The Service is set");
Given("User Navigated to New Application screen");
When("User fills up form as in data row 1");
Then("new SID generated");
}
[Given(#"New Browser Launched")]
public void GivenNewBrowserLaunched()
{
SeleniumContext sl = new SeleniumContext();
this.seleniumContext = sl;
}
[Given(#"Login is successful with ""(.*)"" and ""(.*)""")]
public void GivenLoginIsSuccessfulWithAnd(string userName, string password)
{
SuperTests spr = new SuperTests();
_driver = spr.LaunchURL(seleniumContext.WebDriver);
//seleniumContext.WebDriver = _driver;
LoginPage lg = new LoginPage(_driver);
lg.LoginProcess(userName, password);
}
[Given(#"Set the service to ""(.*)""")]
public void GivenSetTheServiceTo(string serviceId)
{
When("Select a Service from the option "+serviceId);
Then("The Services is changed to the one selected " + serviceId);
}
In other feature
[When(#"Select a Service from the option (.*)")]
public void WhenSelectAServiceFromTheOptionTestTeam(string p0)
{
HomePage mst = new HomePage(seleniumContext.WebDriver);
mst.SetServiceId(p0);
}
The 2 work around what we figured was
Create a new instance of binding class to call the methods or steps as shown below
[Given(#"Set the service to ""(.*)""")]
public void GivenSetTheServiceTo(string serviceId)
{
var serIdSteps = new UK_SetServiceIDSteps(seleniumContext);
serIdSteps.WhenUK_SelectAServiceFromTheOptionTest(serviceId);
serIdSteps.ThenUK_TheServicesIsChangedToTheOneSelected(serviceId);
}
or
tried this which worked as well- basically calling a new method to create a new session. for this I need not create any new instance for Binding class. Called the Step directly.
[Given(#"New Browser Launched")]
public void GivenNewBrowserLaunched()
{
SuperTests spr = new SuperTests();
_driver = spr.LaunchURL("Firefox");
seleniumContext.WebDriver = _driver;
}
public void GivenSetTheServiceTo(string serviceId)
{
When("UK_Select a Service from the option "+serviceId);
Then("UK_The Services is changed to the one selected " + serviceId);
}
Not sure, which is correct way of doing it? Trying to figure it out from Reusable steps point?The latter one is not advised as we need to change the type of browser to launch at multiple place.

Preferred method for persist session token on the server with RIA Services?

I'm using an AuthenticationService derived from AuthenticationBase in the standard business application template for RIA Services and using Forms authentication. I have my own CustomPrincipal that is created by my security infrastructure that I assign to Thread.CurrentPrincipal, so that it can be used by other service calls:
protected override bool ValidateUser(string userName, string password)
{
try
{
using (LoginService service = new LoginService())
{
SessionInfo info = service.Login(userName, password);
Thread.CurrentPrincipal = info.User;
SessionCache.Instance.Save(info);
}
}
catch (Exception e)
{
return false;
}
}
I've discovered, however, when other (non-authentication) RIA services are called, the Thread.CurrentPrincipal is reset to a GenericPrincipal object, so upon initialization of the other services, I override the Initialize() method of the domain service and set Thread.CurrentPrincipal by looking up the login in a session cache:
public override void Initialize(DomainServiceContext context)
{
base.Initialize(context);
if (context.User.Identity.IsAuthenticated)
{
SessionInfo info = SessionCache.Instance.FindByUsername(context.User.Identity.Name);
Thread.CurrentPrincipal = info.User;
}
}
Right now this lookup is being done by Username, because it is easily accessible in the GenericPrincipal, but I'd prefer that I could do the lookup via a session token. Is there a method with RIA services to easily persist a session token on the server during the lifetime of the session? I know I could put this session token in a cookie in the browser to persist it, but it seems like there should be a simpler method to persist a session token across the lifetime of the session. Perhaps the cookie method is the best way to do this, but I just wanted to confirm.

Having a problem with RequiresRole attribute on RIA Domain service

My question is similar to this question. I hope I can provide some more detail and context to get it answered.
So here's some context: I have a simple in-house silverlight (ver 4) app with WCF Ria services that I'm building for our small support team. It uses authentication against a third-party vended database, but all other user information, e.g. FriendlyName and Roles (only 1 role per user) comes from our own database. I'm trying to keep this simple and don't want to implement custom membership and role providers.
I have few domain service operations that I want to restrict to certain roles, so I tried using the RequiresRole attribute like so:
[RequiresRole("Admin", "HelpDesk", "Billing" )]
public RisStudyInfo GetStudyInfo(string accession) {
return ris.GetStudyInfo(accession);
}
On the client side WebContext.Current.User.IsInRole("Admin") returns true, but I always get access denied when calling the service. The RequiresAuthentication attribute works as expected.
Below is the implementation of my AuthenticationService. The User class simply inherits from UserBase and adds the FriendlyName property. Any ideas what I'm doing wrong?
[EnableClientAccess]
public class AuthenticationService : AuthenticationBase<User> {
UserDataService userData = new UserDataService();
protected override bool ValidateUser(string userName, string password) {
var auth = new DatabaseAuthenticator();
return auth.Authenticate(userName, password);
}
protected override User GetAuthenticatedUser(IPrincipal principal) {
User user = null;
if (principal.Identity.IsAuthenticated) {
user = new User();
user.FriendlyName = userData.GetFriendlyName(principal.Identity.Name);
user.Name = principal.Identity.Name;
user.Roles = GetRolesFor(user.Name);
}
return user;
}
private IEnumerable<string> GetRolesFor(string username) {
IList<string> roles = new List<string>();
string role = userData.GetRolesFor(username);
if (role != null)
roles.Add(role);
return roles;
}
Figured it out. At least 2 things wrong. First clue found here. The second clue here
1.Turns out I really do need to write a custom role provider. Only need to implement GetRolesForUser though.
public override string[] GetRolesForUser(string username) {
return new string[] { _userService.GetRolesFor(username) };
}
2.Configure the custom role provider correctly in the web.config
<roleManager cacheRolesInCookie="true" enabled="true" defaultProvider="MyRoleProvider">
<providers>
<add name="MyRoleProvider" type="MyProject.Web.Providers.MyRoleProvider, MyProject.Web"/>
</providers>
</roleManager>
I solved this one by using the local credential store to cache credentials. Whenever a local cred check fails a foreign check occurs and the cache is populated/updated. This was a trivial override of the ValidateUser method. It does mean that stale passwords continue to work until the updated password is used (it will fail locally, pass remotely and trigger an update).
This approach meant that internally everything worked as per an out of the box configuration with no need for any other mods (apart from removing the local create-a-user links).

Consuming WCF Data Services Service Operator (WebGet) async from Silverlight

Having a lot of problems trying to consume a simple service operator in a WCF Data Service from Silverlight. I've verified the following service operator is working by testing it in the browser:
[WebGet]
public IQueryable<SecurityRole> GetSecurityRolesForUser(string userName) {
string currentUsername = HttpContext.Current.User.Identity.Name;
// if username passed in, verify current user is admin and is getting someone else's permissions
if (!string.IsNullOrEmpty(userName)) {
if (!SecurityHelper.IsUserAdministrator(currentUsername))
throw new DataServiceException(401, Properties.Resources.RequiestDeniedInsufficientPermissions);
} else // else nothing passed in, so get the current user's permissions
userName = currentUsername;
return SecurityHelper.GetUserRoles(userName).AsQueryable<SecurityRole>();
}
However no matter how I try using different methods I've found in various online resources, I've been unable to consume the data. I've tried using the BeginExecute() method on boht the DataServiceContext and DataServiceQuery, but I keep getting errors or no data returned in the EndExecute method. I've got to be doing something simple wrong... here's my SL code:
private void InitUserSecurityRoles() {
MyEntities context = new MyEntities(new Uri("http://localhost:9999/MyService.svc"));
context.BeginExecute<SecurityRole>(new Uri("http://localhost:9999/MyService.svc/GetSecurityRolesForUser"), OnComplete, context);
DataServiceQuery<SecurityRole> query = context.CreateQuery<SecurityRole>("GetSecurityRolesForUser");
query.BeginExecute(OnComplete, query);
}
private void OnComplete(IAsyncResult result) {
OnDemandEntities context = result.AsyncState as OnDemandEntities;
var x = context.EndExecute<SecurityRole>(result);
}
Any tips? I'm at a loss right now on how to properly consume a custom service operator from Silverlight (or even sync using my unit test project) from a OData service. I've also verified via Fiddler that I'm passing along the correct authentication stuff as well, even going to far as explicitly set the credentials. Just to be safe, I even removed the logic from the service operator that does the security trimming.
Got it working thanks to #kaevans (http://blogs.msdn.com/b/kaevans):
private void InitUserSecurityRoles() {
DataServiceContext context = new DataServiceContext(new Uri("http://localhost:9999/MyService.svc"));
context.BeginExecute<SecurityRole>(new Uri("/GetSecurityRolesForUser", UriKind.Relative),
(result) => {
SmartDispatcher.BeginInvoke(
() => {
var roles = context.EndExecute<SecurityRole>(result);
UserSecurityRoles = new List<SecurityRole>();
foreach (var item in roles) {
UserSecurityRoles.Add(item);
}
});
}, null);
}
I had to create the SmartDispatcher because this is happening in a ViewModel. Otherwise I could have just used the static Dispatcher.BeginInvoke(). Couldn't get the roles variable to insert into my UserSecurityRoles (type List) directly for sone reason using various techniques, so I just dropped down to adding it manually (code isn't called often nor is it a collection exceeding more than 10 items max... most are <5).

Resources