// // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.IdentityModel.SecurityTokenService; using System.Configuration; using Trade.Utility; using System.Security.Cryptography.X509Certificates; using Microsoft.IdentityModel.Claims; using Microsoft.IdentityModel.Configuration; using Microsoft.IdentityModel.Protocols.WSTrust; namespace Trade.ActiveStsImplementation { /// /// Implementation of a Custom SecurityTokenService. /// public class CustomSecurityTokenService : SecurityTokenService { /// /// Creates an instance of CustomSecurityTokenService. /// /// Configuration for this SecurityTokenService. public CustomSecurityTokenService(SecurityTokenServiceConfiguration configuration) : base(configuration) { string signingCertificate = ConfigurationManager.AppSettings["SigningCertificateName"]; // Setup our certificate the STS is going to use to sign the issued tokens configuration.SigningCredentials = new X509SigningCredentials( CertificateUtil.GetCertificate(StoreName.TrustedPeople, StoreLocation.LocalMachine, signingCertificate), "http://www.w3.org/2000/09/xmldsig#rsa-sha1", "http://www.w3.org/2000/09/xmldsig#sha1"); } /// /// This methods returns the configuration for the token issuance request. The configuration /// is represented by the Scope class. In our case, we are only capable to issue a token for a /// single RP identity represented by CN=localhost. /// /// The caller's principal /// The incoming RST /// protected override Scope GetScope(IClaimsPrincipal principal, RequestSecurityToken request) { string encryptingCertificate = ConfigurationManager.AppSettings["EncryptingCertificateName"]; // We only support a single RP identity represented by CN=localhost. Set the RP certificate for encryption X509EncryptingCredentials encryptingCredentials = new X509EncryptingCredentials( CertificateUtil.GetCertificate(StoreName.TrustedPeople, StoreLocation.LocalMachine, encryptingCertificate)); // Create the scope using the request AppliesTo address, the STS signing certificate and the encryptingCredentials for the RP. if (request.AppliesTo == null || request.AppliesTo.Uri == null) { throw new InvalidRequestException("Cannot determine the AppliesTo address from the RequestSecurityToken."); } Scope scope = new Scope(request.AppliesTo.Uri.AbsoluteUri, SecurityTokenServiceConfiguration.SigningCredentials, encryptingCredentials); // Set the replyTo address. In WS-Federation passive case this value is used as the endpoint // where the user is redirected to. scope.ReplyToAddress = scope.AppliesToAddress; return scope; } /// /// This method returns the content of the issued token. The content is represented as a set of /// IClaimIdentity intances, each instance corresponds to a single issued token. /// /// The scope that was previously returned by GetScope method. /// The caller's principal. /// The incoming RST. /// protected override IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal principal, RequestSecurityToken request, Scope scope) { IClaimsIdentity callerIdentity = (IClaimsIdentity)principal.Identity; ClaimsIdentityCollection outputClaimsCollection = new ClaimsIdentityCollection(); // Create new identity and copy content of the caller's identity into it (including the existing delegate chain) IClaimsIdentity outputIdentity = new ClaimsIdentity(); CopyClaims(callerIdentity, outputIdentity); // If there is an ActAs token in the RST, add and return the claims from it as the top-most identity // and put the caller's identity into the Delegate property of this identity. if (request.ActAs != null) { IClaimsIdentity actAsIdentity = new ClaimsIdentity(); CopyClaims(request.ActAs.GetSubject().First(), actAsIdentity); // Find the last delegate in the actAs identity IClaimsIdentity lastActingVia = actAsIdentity; while (lastActingVia.Actor != null) { lastActingVia = lastActingVia.Actor; } // Put the caller's identity as the last delegate to the ActAs identity lastActingVia.Actor = outputIdentity; // Return the actAsIdentity instead of the caller's identity in this case outputIdentity = actAsIdentity; } return outputIdentity; } /// /// Do a deep-copy of IClaimsIdentity except the issuer. /// /// Source Identity. /// Destination Identity. private void CopyClaims(IClaimsIdentity srcIdentity, IClaimsIdentity dstIdentity) { foreach (Claim claim in srcIdentity.Claims) { // We don't copy the issuer because it is not needed in this case. The STS always issues claims // using its own identity. Claim newClaim = new Claim(claim.ClaimType, claim.Value, claim.ValueType); // copy all claim properties foreach (string key in claim.Properties.Keys) { newClaim.Properties.Add(key, claim.Properties[key]); } // add claim to the destination identity dstIdentity.Claims.Add(newClaim); } } } }