Hi,
Is anybody else trying to CASify Sharepoint 2010?
Sharepoint 2010 uses Saml 1.1 Tokens internally (Farm).
MS Recommendation: "For new implementations of SharePoint Server 2010,
use claims-based authentication."
Claims-based authentication is based on Windows Identity Foundation.
WS-Federation:
http://msdn.microsoft.com/en-us/library/bb608217.aspx
This 3 "Windows Identity Foundation" Modules take care of WS-Federation
protocol in IIS 7:
ClaimsPrincipalHttpModule
WSFederationAuthenticationModule
SessionAuthenticationModule
(Sharepoint 2010 got its own versions of this
modules:Microsoft.SharePoint.IdentityModel.SPWindowsClaimsAuthenticationHttpModule,
Microsoft.SharePoint.IdentityModel.SPFederationAuthenticationModule,
Microsoft.SharePoint.IdentityModel.SPSessionAuthenticationModule)
SP 2010 Claims based authentication works with out of the box ADFS 2.0
and Windows LiveID.
(http://www.wictorwilen.se/Post/Visual-guide-to-Windows-Live-ID-authentication-with-SharePoint-2010-part-1.aspx)
Main Difference between WS-Federation and CAS: The Application recives
the SAML 1.1 token through hidden POST there is no direct communication
between Identity Provider and the Relying Party.
WS-Federation Passive Requestor Profile Step 6 – Return requestor token
(setTimeout('document.forms[0].submit() </script>)
WS-Federation Passive Requestor Profile Step 7 – POST requestor token
(disable javascript to see the button with the form-hidden saml1.1 token)
So we need a "Transitioning" oder "Bridging" Token Service.
Proof of concept:
http://blogs.pointbridge.com/Blogs/nielsen_travis/Pages/Post.aspx?_ID=40
replace the Custom STS Login.apsx.cs with the attached one and change
the methods in customsecuritytokenservice accordingly.
Another Example for the bridging concept from Dominick Baier and Matias
Woloski: http://www.leastprivilege.com/StarterSTS15.aspx
Who builds a CAS bridge for this :) ?
best regards
--
Markus
--
You are currently subscribed to [email protected] as:
[email protected]
To unsubscribe, change settings or access archives, see
http://www.ja-sig.org/wiki/display/JSG/cas-user// changes to the code in GetScope and GetOutputClaimsIdentity according the
example from Travis Nielsen
protected override Scope GetScope( IClaimsPrincipal principal,
RequestSecurityToken request )
{
ValidateAppliesTo( request.AppliesTo );
//
// Note: The signing certificate used by default has a Distinguished
name of "CN=STSTestCert",
// and is located in the Personal certificate store of the Local
Computer. Before going into production,
// ensure that you change this certificate to a valid CA-issued
certificate as appropriate.
//
Scope scope = new Scope( request.AppliesTo.Uri.OriginalString,
SecurityTokenServiceConfiguration.SigningCredentials );
string encryptingCertificateName = WebConfigurationManager.AppSettings[
"EncryptingCertificateName" ];
if ( !string.IsNullOrEmpty( encryptingCertificateName ) )
{
// Important note on setting the encrypting credentials.
// In a production deployment, you would need to select a
certificate that is specific to the RP that is requesting the token.
// You can examine the 'request' to obtain information to determine
the certificate to use.
scope.EncryptingCredentials = new X509EncryptingCredentials(
CertificateUtil.GetCertificate( StoreName.My, StoreLocation.LocalMachine,
encryptingCertificateName ) );
}
else
{
// If there is no encryption certificate specified, the STS will
not perform encryption.
// This will succeed for tokens that are created without keys
(BearerTokens) or asymmetric keys.
scope.TokenEncryptionRequired = false;
}
// Set the ReplyTo address for the WS-Federation passive protocol
(wreply). This is the address to which responses will be directed.
// In this template, we have chosen to set this to the AppliesToAddress.
if (scope.AppliesToAddress.ToLower() == "urn:bbexpress:cas")
{
scope.ReplyToAddress =
"https://bbexpress.hrz.tu-darmstadt.de/_trust/";
}
else
{
scope.ReplyToAddress = scope.AppliesToAddress;
}
return scope;
}
/// <summary>
/// This method returns the claims to be issued in the token.
/// </summary>
/// <param name="principal">The caller's principal.</param>
/// <param name="request">The incoming RST, can be used to obtain addtional
information.</param>
/// <param name="scope">The scope information corresponding to this
request.</param>
/// <exception cref="ArgumentNullException">If 'principal' parameter is
null.</exception>
/// <returns>The outgoing claimsIdentity to be included in the issued
token.</returns>
protected override IClaimsIdentity GetOutputClaimsIdentity(
IClaimsPrincipal principal, RequestSecurityToken request, Scope scope )
{
if ( null == principal )
{
throw new ArgumentNullException( "principal" );
}
ClaimsIdentity outputIdentity = new ClaimsIdentity();
// Issue custom claims.
// TODO: Change the claims below to issue custom claims required by
your application.
// Update the application's configuration file too to reflect new
claims requirement.
var CAS20Claims = HttpContext.Current.Session["CAS20Claims"] as
Dictionary<string, string>;
foreach (var casClaim in CAS20Claims)
{
outputIdentity.Claims.Add(new Claim(casClaim.Key, casClaim.Value));
}
//outputIdentity.Claims.Add( new Claim(
System.IdentityModel.Claims.ClaimTypes.Name, principal.Identity.Name ) );
//outputIdentity.Claims.Add( new Claim( ClaimTypes.Role, "Manager" ) );
return outputIdentity;
}
}
using System;
using System.Web.Security;
using System.Web;
using System.Net;
using System.IO;
using System.Collections.Generic;
using DotNetCasClient.Validation.Schema.Cas20;
using DotNetCasClient.Validation;
//using DotNetCasClient.Security;
using System.Xml.Serialization;
using System.Xml;
public partial class Login : System.Web.UI.Page
{
public const string CASHOST = "https://sso.hrz.tu-darmstadt.de/";
private string ticket="";
private string UrlToAuthenticate =
"https://bbexpress.hrz.tu-darmstadt.de:54322/casts/Login.aspx";
public enum Method { GET, POST };
protected void Page_Load( object sender, EventArgs e )
{
// cas20 Angepasst
// based on custom sts by Travis Nielsen:
http://blogs.pointbridge.com/Blogs/nielsen_travis
//CAS20Validator casval = new CAS20Validator();
if (Request["ticket"] == null)
{
if (Request.QueryString["ReturnUrl"] != null)
HttpContext.Current.Session.Add("OriginalQueryString",
Request.QueryString.ToString());
string SignonUrl = CASHOST + "login?" + "service=" +
UrlToAuthenticate + "&" + Request.QueryString.ToString();
//string SignonUrl = CASHOST + "login?" + "service=" +
UrlToAuthenticate;
Response.Redirect(SignonUrl);
}
else
{
ticket = Request["ticket"];
string validateurl = CASHOST + "serviceValidate?" + "service=" +
UrlToAuthenticate + "&ticket=" + ticket;
string retval = RetrieveResponseFromServer(validateurl, ticket);
// ********CAS client
ServiceResponse serviceResponse;
try
{
serviceResponse = ServiceResponse.ParseResponse(retval);
}
catch (InvalidOperationException)
{
throw new TicketValidationException ("CAS Server response does
not conform to CAS 2.0 schema");
}
AuthenticationSuccess authSuccessResponse =
(AuthenticationSuccess)serviceResponse.Item;
Dictionary<string, string> claims = GetClaims(retval);
HttpContext.Current.Session.Add("CAS20Claims", claims);
FormsAuthentication.SetAuthCookie("CAS Test", false);
Response.Redirect("default.aspx?" +
HttpContext.Current.Session["OriginalQueryString"]);
//Response.Redirect("default.aspx");
}
}
private Dictionary<string, string> GetClaims(string castoken)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(castoken);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("cas", "http://www.yale.edu/tp/cas");
XmlElement root = doc.DocumentElement;
Dictionary<string, string> claims = new Dictionary<string, string>();
string mail = root.SelectSingleNode("//cas:mail", nsmgr).InnerText;
string tuid = root.SelectSingleNode("//cas:cn", nsmgr).InnerText;
string role = root.SelectSingleNode("//cas:__AUTHUSERCONTEXT__",
nsmgr).InnerText;
string sn = root.SelectSingleNode("//cas:surname", nsmgr).InnerText;
string gn = root.SelectSingleNode("//cas:givenName", nsmgr).InnerText;
string groups = root.SelectSingleNode("//cas:groupMembership",
nsmgr).InnerText;
if (!String.IsNullOrEmpty(tuid))
claims.Add(Microsoft.IdentityModel.Claims.ClaimTypes.NameIdentifier, tuid);
if (!String.IsNullOrEmpty(sn))
claims.Add(Microsoft.IdentityModel.Claims.ClaimTypes.Surname, sn);
if (!String.IsNullOrEmpty(gn))
claims.Add(Microsoft.IdentityModel.Claims.ClaimTypes.GivenName, gn);
if (!String.IsNullOrEmpty(mail))
claims.Add(Microsoft.IdentityModel.Claims.ClaimTypes.Email, mail);
if (!String.IsNullOrEmpty(role))
claims.Add(Microsoft.IdentityModel.Claims.ClaimTypes.Role, role);
if (!String.IsNullOrEmpty(groups))
claims.Add(Microsoft.IdentityModel.Claims.ClaimTypes.GroupSid,
groups);
return claims;
}
public string RetrieveResponseFromServer(string url, string ticket)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "GET";
req.ServicePoint.Expect100Continue = false;
req.ContentType = "text/xml";
req.UserAgent = ".NET Framework Test Client";
req.Timeout = 20000;
HttpWebResponse response = (HttpWebResponse)req.GetResponse();
Stream responseStream = response.GetResponseStream();
if (responseStream != null)
{
using (StreamReader responseReader = new
StreamReader(responseStream))
{
return responseReader.ReadToEnd();
}
}
else
{
throw new ApplicationException("Unable to retrieve response
stream.");
}
}
}