//
// 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);
}
}
}
}