I have a class that implements IAuthorizationPolicy. I set up a custom Principal object based on the logged in user which has all of my base level roles (I have also done this using claims). Now I would like to change the roles that a particular principal has depending on a key value passed in as a message parameter.
The problem I am having is that the request message cannot be read in the authorization policy class because I don't have access to write the message back to the request context. I can copy and read the message in a ServiceAuthorizationManager derived class using an override of the CheckAccess method. However, I have to ensure that the GetAuthorizationPolicies method has already been called prior to doing that.
I am looking for suggestions on how I can vary the roles on a principal, based on whether or not the message contains a particular parameter. Basically, when the Evaluate method id called on the policy I want to do something like this:
string myObjectId = null;
if (!messageCopy.IsEmpty)
{
System.Xml.XmlDictionaryReader xdr = messageCopy.GetReaderAtBodyContents();
xdr.ReadToDecendant("objectId");
if (xdr.Read())
{
myObjectId = xdr.ReadContentAsString();
}
xdr.Close();
}
messageCopy.Close();
ClaimSet claims = (myObjectId != null) ?
MapClaims(identity, myObjectId) : MapClaims(identity);
DefaultPrincipal principal = new DefaultPrincipal(identity, claims);
After an entire day of attempted failures, I gave up on trying to read the message body and used an easier method, adding a SOAP message header. When calling the service I now perform the following:
using (new OperationContextScope((IContextChannel)myService)) {
OperationContext.Current.OutgoingMessageHeaders.Add(
MessageHeader.CreateHeader("objectId", "http://tempuri.org/", "object value"));
myService.BeginMyOperation(parm, callback, state);
}
Then in my service authorization policy's Evaluate method I do this:
int index = OperationContext.Current.IncomingMessageHeaders.FindHeader(
"objectId", "http://tempuri.org/");
string myObjectId = (index < 0) ? null :
OperationContext.Current.IncomingMessageHeaders.GetHeader<string>(index);
ClaimSet claims = (myObjectId != null) ?
MapClaims(identity, myObjectId) : MapClaims(identity);
DefaultPrincipal principal = new DefaultPrincipal(identity, claims);
I run into the same situation while developing WebAPI security and I choosen the next approach:
Method that recieves argument creates AuthorizationContext where it passes the argument as Resource claim
My custom ClaimsAuthorizationManager then can get argument from AuthorizationContext.Resource and use it from authorization.
Related
I wish to correlate all the calls throughout Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents (OnRedirectToIdentityProvider, OnAuthorizationCodeReceived etc).
I wish to set the 'state' parameter in OnRedirectToIdentityProvider as a GUID to later corralate the calls in logs, for example
OnRedirectToIdentityProvider = async context =>
{
var myGuid = Guid.NewGuid().ToString();
context.ProtocolMessage.State = myGuid;
_log.LogInformation("OnRedirectToIdentityProvider: {0}", myGuid);
...
},
OnAuthorizationCodeReceived = async context =>
{
_log.LogInformation("OnAuthorizationCodeReceived: {0}", context.ProtocolMessage.State);
...
},
...
In the docs it is said that identityserver will echo back the state value on the token response. http://docs.identityserver.io/en/latest/endpoints/authorize.html
I also read that client is responsible for the validation of this property.
The question is:
I couldn't find any specific resource about when I want to use 'state' property, whether the validation is handled automatically by the middleware or should I handle the validation myself in a callback function?
Is there any security risks I should consider when using GUID in a 'state' parameter?
Pros/cons I should consider?
Regards,
A
This is a valid approach to use GUID as ProtocolMessage.State property value.
After setting ProtocolMessage.State in OnRedirectToIdentityProvider event
context.ProtocolMessage.State = myGuid;
Found out from source code that data is being deserialized with StateDataFormat.Unprotect() method. I used this to debug
context.Options.StateDataFormat.Unprotect("CfDJ8...yr7Rpx3DyQMwPw")
'state' value in query is actually a serialized AuthenticationProperties class.
The AuthenticationProperties class is generated by the middleware and ProtocolMessage.State value is actually stored as AuthenticationProperties.Items["OpenIdConnect.Userstate"] in the response.
As mentioned in the comment, middleware handles the validation of state.
I am using ServiceStack's SocialBootstrapApi and it contains a class CustomUserSession that I can use to override the OnRegistered method. I want to override it because I am attempting to obtain information about the registration so that I can publish an event that a new user has registered. This handler provides an instance of the RegistrationService that handled the registration but not anything about the registration request itself or the resulting UserAuth instance. For instance, I'd like to get the e-mail address used to register.
public override void OnRegistered(IServiceBase registrationService)
{
base.OnRegistered(registrationService);
// Ideally, I could do get the registered user's primary e-mail address from the UserAuth instance.
var primaryEmail = ((RegistrationService) registrationService)
.UserAuthRepo
.GetUserAuth(this, null) //<--- 'this' is a mostly empty session instance
.PrimaryEmail;
}
This of course doesn't work because the session instance I'm using for the GetUserAuth call doesn't contain any of the necessary authentication information to be useful for looking up the user's authentication information. So GetUserAuth returns null as you would expect. So how should I go about obtaining this information? Would it be incorrect design for the OnRegistered handler to be passed the UserAuth instance created by the RegistrationService?
public interface IAuthSession
{
...
void OnRegistered(IServiceBase registrationService, UserAuth userAuth); // <-- new signature
...
}
That would be convenient! :)
Or perhaps there's another way to go about this?
Thanks in advance.
So how should I go about obtaining this information?
You should be able to access all the data of the Registration request via the registrationService. You just have to do a little digging and casting...
public override void OnRegistered(IServiceBase registrationService)
{
base.OnRegistered(registrationService);
var requestContext = (HttpRequestContext)registrationService.RequestContext;
var dto = ((Registration)requestContext.Dto);
var primaryEmail = dto.Email;
}
Would it be incorrect design for the OnRegistered handler to be passed the UserAuth instance created by the RegistrationService?
I'll leave design decisions to the professionals. The above code should work. The casting seems a bit ugly but all the necessary data is there.
I do not like hack into SS, so I chose to select user auth info from UserAuth collection by dto.UserName
For a WCF project I'm working on, I need to implement some kind of audit log for the entities we're persisting. Basically the audit trail will consist of 4 mandatory fields
CreatedDateTime
CreatedUserID
UpdatedDateTime
UpdatedUserID
I'm trying to implement this through nHibernate Event Listeners in my DataAccess layer as I think this should not be in the domain layer. So far, I've got the DateTime stuff working as expected, but haven't been able to figure out how to retrieve the userID in the event listener. Ideally, I would like to have the user id as some sort of custom data attached to the nHibernate session object.
Any suggestion is greatly appreciated.
The .Net framework already has builtin support for contextual user identity information: Thread.CurrentPrincipal http://msdn.microsoft.com/en-us/library/system.threading.thread.currentprincipal.aspx
Use one of the available IPrincipal implementations or create your own - it's easy. Then set the property early on, in some "begin-request" method.
There is also HttpContext.User to be aware of in ASP.NET code.
Here's how I'm doing it but I don't have experience doing this with WCF. Note that this requires a reference to System.Web.
/// <summary>
/// Returns the user name of the current user. Gets user name from HttpContext if running as a web app, else WindowsIdentity.
/// </summary>
private static string GetUserName()
{
var identity = HttpContext.Current == null ? WindowsIdentity.GetCurrent() : HttpContext.Current.User.Identity;
if (identity == null)
{
throw new Exception("Identity could not be determined.");
}
// Remove domain name if present
var s = identity.Name;
var stop = s.IndexOf("\\", StringComparison.InvariantCultureIgnoreCase);
return (stop > -1) ? s.Substring(stop + 1, s.Length - stop - 1).ToUpper() : s;
}
I have implemented NHibernate custom context (ICurrentSessionContext).
In this context I inject the NHibernate session so I have Session per call pattern setup.
Ok, now I have made an interceptor that takes userId of the current logged user.
Now I do this:
public ISession CurrentSession()
{
// Get the WCF InstanceContext:
var contextManager = OperationContext.Current.InstanceContext.Extensions.Find<NHibernateContextManager>();
if (contextManager == null)
{
throw new InvalidOperationException(
#"There is no context manager available.
Check whether the NHibernateContextManager is added as InstanceContext extension.
Make sure the service is being created with the NhServiceHostFactory.
This Session Provider is intended only for WCF services.");
}
var session = contextManager.Session;
AuditLogInterceptor interceptor = new AuditLogInterceptor();
if (session == null)
{
session = this._factory.OpenSession(interceptor);
interceptor.Session = session;
contextManager.Session = session;
}
return contextManager.Session;
}
My AuditLogInterceptor takes UserId but I don't know how (from where) to get this userId.
If your user is authenticated you can use:
OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name
I assume that the current user is being set as the principal on the current thread?
If so, something like this is what you need:
var userName = Thread.CurrentPrincipal.Identity.Name;
There is some additional information in this article that may prove helpful.
Greetings, what is the problem that when I try to set credentials for my factory as follows:
ChannelFactory<IWCFSeekService> factory = Factory;
if (factory != null)
{
factory.Credentials.UserName.UserName = CServiceCredentials.Instance.Username;
_Channel = factory.CreateChannel();
}
I get an exception that object is read-only. It occurs when I want to set username.
Yes, the MSDN documentation is pretty clear:
C#
public ClientCredentials Credentials { get; }
The property only has a get accessor - no set accessor --> it's readonly.
Also in the MSDN docs:
Remarks
The ClientCredentials object is stored
as a type of endpoint behavior and can
be accessed through the Behaviors
property.
The OnOpened method initializes a
read-only copy of the
ClientCredentials object for the
factory.
So what is it you're doing to do here??
UPDATE: you cannot set the user credentials that your client proxy is supposed to use on the channel factory. See this excellent blog post on how to do it anyway - with a bit of a detour:
first, remove the default endpoint behavior from the factory
secondly, instantiate your own credentials
thirdly, set those new credentials as new endpoint behavior on factory
// step one - find and remove default endpoint behavior
var defaultCredentials = factory.Endpoint.Behaviors.Find<ClientCredentials>();
factory.Endpoint.Behaviors.Remove(defaultCredentials);
// step two - instantiate your credentials
ClientCredentials loginCredentials = new ClientCredentials();
loginCredentials.UserName.UserName = CServiceCredentials.Instance.Username;
loginCredentials.UserName.Password = “Password123″;
// step three - set that as new endpoint behavior on factory
factory.Endpoint.Behaviors.Add(loginCredentials); //add required ones
Seems a bit odd and complicated, but that seems to be the one and only way to achieve this!
To complete this answer, the actual way in which it worked for everyone as explained at
http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/4668e261-0fd0-4ca5-91d2-497aa479f2a9/
You need not to remove, but override found credentials:
var credentialBehaviour = factory.Endpoint.Behaviors.Find < ClientCredentials > ();
credentialBehaviour.UserName.UserName = "test";
credentialBehaviour.UserName.Password = "test";
This has solved my problem.
This will not happen if the service reference is added through -> Add service reference ->Advanced->Add Web Reference-> Url/wsdl (local disk file).
The reference.cs file generated is different and will allow you to set credentials.
The error is because you might have added the reference through first screen itself (Add service reference)