multitenant custom active STS endpoint (that works with Office365) – async GetScope validates username token


Here we show our code for the async form of the STS callback “GetScope” method. This calls our own username token processing router to validate the token provided by office 365. The router gets the token from the method’s principal, pre-populated with the bootstrap property by the WCF-invoked “stub” usernametoken processing we discussed in the previous memo. A pretty ordinary implementation of a username token processor then verifies the user name and password within the token, using parameters from MyContext. These are those parameters parsed by the service behavior earlier, (see multitenant custom active STS endpoint (that works with Office365) – token service factory http://wp.me/p1fcz8-4fy) using the additional path elements on the addressed endpoint that we use to indicate the tenant (and other things like attribute contracts), etc.

Note the coding style for async methods referencing the context. Because implied threads are involved one must declare the container for the current context outside the delegate body, which then references the local stack before the thread is cloned (presumably).

        protected override IAsyncResult BeginGetScope(IClaimsPrincipal principal, RequestSecurityToken request, 
                                                            AsyncCallback callback, object state)
        {
            var myc = MyContext.Current;
            Func<Scope> func = () =>
            {
                var ci = principal.Identity as IClaimsIdentity;

                var confirm = new SpecialUserNamePasswordValidatorSecurityTokenHandler(myc);
                ClaimsPrincipal cp = new ClaimsPrincipal(confirm.ValidateToken((principal.Identity as IClaimsIdentity).BootstrapToken));
                ci.Claims.AddRange((cp.Identity as IClaimsIdentity).Claims);

                var scope = new Scope(request.AppliesTo.Uri.AbsoluteUri, this.SecurityTokenServiceConfiguration.SigningCredentials);
                \\LYNC rule
                if (request.AppliesTo.Uri.AbsoluteUri.ToLower().Contains("rapmls"))
                {
                    scope = new Scope("urn:federation:MicrosoftOnline", this.SecurityTokenServiceConfiguration.SigningCredentials);
                }

                string encryptingCertificateName = WebConfigurationManager.AppSettings["CERT_" + scope.AppliesToAddress];
                if (!string.IsNullOrEmpty(encryptingCertificateName))
                {
                    scope.EncryptingCredentials = new X509EncryptingCredentials(CertificateUtil.GetCertificate(StoreName.My, 
                                                            StoreLocation.LocalMachine, encryptingCertificateName));
                }
                else
                {
                    scope.TokenEncryptionRequired = false;
                    scope.SymmetricKeyEncryptionRequired = false;
                }

                if (!string.IsNullOrEmpty(request.ReplyTo))
                {
                    scope.ReplyToAddress = request.ReplyTo;
                } else {
                    scope.ReplyToAddress = scope.AppliesToAddress;
                }
                return scope;
            };
            return func.BeginInvoke(callback, state);
        }

        protected override Scope EndGetScope(IAsyncResult result)
        {
            result.AsyncWaitHandle.WaitOne();
            var del = ((AsyncResult)result).AsyncDelegate as Func<Scope>;
            return del.EndInvoke(result);
        }

        /// <summary>
        /// This method establishes the relying party endpoints for receiving the assertion.
        /// Also, sets the audience fields in the SAML assertion and optionally establishes the encryption certificates
        /// of the relying party. Certificate to be found in machine's MyStore.
        /// </summary>
        /// <param name="principal">IClaimsPrincipal object</param>
        /// <param name="request">Incoming request</param>
        /// <returns>Returns the relying party endpoint information</returns>
        protected override Scope GetScope(IClaimsPrincipal principal, RequestSecurityToken request)
        {
            return EndGetScope(BeginGetScope(principal, request, null, null));
        }

    }






    #region context
    public class MyContextBehaviorAttribute : Attribute, IServiceBehavior
    {
        public void AddBindingParameters(ServiceDescription serviceDescription,
                                         ServiceHostBase serviceHostBase,
                                         System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints,
                                         BindingParameterCollection bindingParameters)
        {}

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
            {
                foreach (EndpointDispatcher ed in cd.Endpoints)
                {
                    ed.DispatchRuntime.MessageInspectors.Add(new MyContextMessageInspector());
                }
            }
        }

        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }         
    }

    public class MyContext : IExtension<OperationContext>
    {
        public string colc { get; set; }
        public string vcrd { get; set; }
        public string linkid { get; set; }
        public string variety { get; set; }

        public static MyContext Current
        {
            get { return OperationContext.Current.Extensions.Find<MyContext>(); }
        }

        public static OperationContext Context
        {
            get { return OperationContext.Current; }
        }

        public void Attach(OperationContext owner)
        {
            List<string> seg = new List<string>();
            foreach (var segment in owner.IncomingMessageHeaders.To.Segments) {
                seg.Add(segment.ToLowerInvariant());
            };
            var len = seg.Count();

            var indexer = seg.IndexOf("office365/") ;
            if (indexer != -1)
            {
                if(len - indexer < 4)
                {
                    throw new SecurityException("bad office36 endpoint address");
                }
                else
                {
                    variety = owner.IncomingMessageHeaders.To.Segments[len - 4].TrimEnd('/');
                    vcrd = owner.IncomingMessageHeaders.To.Segments[len - 3].TrimEnd('/');
                    linkid = owner.IncomingMessageHeaders.To.Segments[len - 2].TrimEnd('/');
                    colc = owner.IncomingMessageHeaders.To.Segments[len - 1];
                }
            }
        }

        public void Detach(OperationContext owner)
        {        }


    }

    public class MyContextMessageInspector : IDispatchMessageInspector
    {
        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request,
                                          IClientChannel channel,
                                          InstanceContext instanceContext)
        {
            OperationContext.Current.Extensions.Add(new MyContext());
            return request.Headers.MessageId;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            OperationContext.Current.Extensions.Remove(MyContext.Current);
        }
    }
 
Advertisements

About home_pw@msn.com

Computer Programmer who often does network administration with focus on security servers. Very strong in Microsoft Azure cloud!
This entry was posted in coding theory. Bookmark the permalink.