public static SecurityToken MakeBootstrapSecurityToken() { Saml2NameIdentifier identifier = new Saml2NameIdentifier("http://localhost/Echo"); Saml2Assertion assertion = new Saml2Assertion(identifier); assertion.Issuer = new Saml2NameIdentifier("idp1.test.oio.dk"); assertion.Subject = new Saml2Subject(new Saml2NameIdentifier("Casper", new Uri("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"))); Saml2Attribute atribute = new Saml2Attribute("dk:gov:saml:attribute:AssuranceLevel", "2"); atribute.NameFormat = new Uri("urn:oasis:names:tc:SAML:2.0:attrname-format:basic"); assertion.Statements.Add(new Saml2AttributeStatement(atribute)); return new Saml2SecurityToken(assertion); }
//public CustomSaml2SecurityTokenHandler(SecurityTokenHandlerConfiguration configuration) //{ // SecurityTokenHandlerConfiguration handlerConfig = configuration ?? new SecurityTokenHandlerConfiguration // { // AudienceRestriction = {AudienceMode = AudienceUriMode.Never}, // CertificateValidationMode = X509CertificateValidationMode.None, // RevocationMode = X509RevocationMode.NoCheck, // MaxClockSkew = new TimeSpan(50000000), // CertificateValidator = X509CertificateValidator.None // }; // Configuration = handlerConfig; //} //public CustomSaml2SecurityTokenHandler() // : this(null) //{ //} protected override void WriteAttributeValue(XmlWriter writer, string value, Saml2Attribute attribute) { var sb = new StringBuilder("<a>"); sb.Append(value); sb.Append("</a>"); byte[] rawValue = new UTF8Encoding().GetBytes(sb.ToString()); using ( XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(rawValue, XmlDictionaryReaderQuotas.Max)) { reader.ReadStartElement("a"); while (reader.NodeType != XmlNodeType.EndElement || (reader.NodeType == XmlNodeType.EndElement && reader.Name != "a")) { writer.WriteNode(reader, false); } reader.ReadEndElement(); reader.Close(); } }
/// <summary> /// Writes the saml:Attribute value. /// </summary> /// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Attribute"/>.</param> /// <param name="value">The value of the attribute being serialized.</param> /// <param name="attribute">The <see cref="Saml2Attribute"/> to serialize.</param> /// <remarks>By default the method writes the value as a string.</remarks> /// <exception cref="ArgumentNullException">The input parameter 'writer' is null.</exception> protected virtual void WriteAttributeValue(XmlWriter writer, string value, Saml2Attribute attribute) { if (writer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } writer.WriteString(value); }
/// <summary> /// Writes the <saml:Attribute> element. /// </summary> /// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Attribute"/>.</param> /// <param name="data">The <see cref="Saml2Attribute"/> to serialize.</param> protected virtual void WriteAttribute(XmlWriter writer, Saml2Attribute data) { if (null == writer) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer"); } if (null == data) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data"); } // <Attribute> writer.WriteStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace); // @Name - required writer.WriteAttributeString(Saml2Constants.Attributes.Name, data.Name); // @NameFormat - optional if (null != data.NameFormat) { writer.WriteAttributeString(Saml2Constants.Attributes.NameFormat, data.NameFormat.AbsoluteUri); } // @FriendlyName - optional if (null != data.FriendlyName) { writer.WriteAttributeString(Saml2Constants.Attributes.FriendlyName, data.FriendlyName); } // @OriginalIssuer - optional if (null != data.OriginalIssuer) { writer.WriteAttributeString(Saml2Constants.Attributes.OriginalIssuer, ClaimType2009Namespace, data.OriginalIssuer); } string xsiTypePrefix = null; string xsiTypeSuffix = null; if (!StringComparer.Ordinal.Equals(data.AttributeValueXsiType, ClaimValueTypes.String)) { // ClaimValueTypes are URIs of the form prefix#suffix, while xsi:type should be a QName. // Hence, the tokens-to-claims spec requires that ClaimValueTypes be serialized as xmlns:tn="prefix" xsi:type="tn:suffix" int indexOfHash = data.AttributeValueXsiType.IndexOf('#'); xsiTypePrefix = data.AttributeValueXsiType.Substring(0, indexOfHash); xsiTypeSuffix = data.AttributeValueXsiType.Substring(indexOfHash + 1); } // <AttributeValue> 0-OO (nillable) foreach (string value in data.Values) { writer.WriteStartElement(Saml2Constants.Elements.AttributeValue, Saml2Constants.Namespace); if (null == value) { writer.WriteAttributeString("nil", XmlSchema.InstanceNamespace, XmlConvert.ToString(true)); } else if (value.Length > 0) { if ((xsiTypePrefix != null) && (xsiTypeSuffix != null)) { writer.WriteAttributeString("xmlns", ProductConstants.ClaimValueTypeSerializationPrefix, null, xsiTypePrefix); writer.WriteAttributeString("type", XmlSchema.InstanceNamespace, String.Concat(ProductConstants.ClaimValueTypeSerializationPrefixWithColon, xsiTypeSuffix)); } this.WriteAttributeValue(writer, value, data); } writer.WriteEndElement(); } // </Attribute> writer.WriteEndElement(); }
/// <summary> /// Reads an attribute value. /// </summary> /// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2Attribute"/>.</param> /// <param name="attribute">The <see cref="Saml2Attribute"/>.</param> /// <returns>The attribute value as a string.</returns> /// <exception cref="ArgumentNullException">The input parameter 'reader' is null.</exception> protected virtual string ReadAttributeValue(XmlReader reader, Saml2Attribute attribute) { // This code was designed realizing that the writter of the xml controls how our // reader will report the NodeType. A completely differnet system (IBM, etc) could write the values. // Considering NodeType is important, because we need to read the entire value, end element and not loose anything significant. // // Couple of cases to help understand the design choices. // // 1. // "<MyElement xmlns=""urn:mynamespace""><another>complex</another></MyElement><sibling>value</sibling>" // Could result in the our reader reporting the NodeType as Text OR Element, depending if '<' was entitized to '<' // // 2. // " <MyElement xmlns=""urn:mynamespace""><another>complex</another></MyElement><sibling>value</sibling>" // Could result in the our reader reporting the NodeType as Text OR Whitespace. Post Whitespace processing, the NodeType could be // reported as Text or Element, depending if '<' was entitized to '<' // // 3. // "/r/n/t " // Could result in the our reader reporting the NodeType as whitespace. // // Since an AttributeValue with ONLY Whitespace and a complex Element proceeded by whitespace are reported as the same NodeType (2. and 3.) // the whitespace is remembered and discarded if an found is found, otherwise it becomes the value. This is to help users who accidently put a space when adding claims in ADFS // If we just skipped the Whitespace, then an AttributeValue that started with Whitespace would loose that part and claims generated from the AttributeValue // would be missing that part. // if (reader == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } string result = String.Empty; string whiteSpace = String.Empty; reader.ReadStartElement(Saml2Constants.Elements.AttributeValue, Saml2Constants.Namespace); while (reader.NodeType == XmlNodeType.Whitespace) { whiteSpace += reader.Value; reader.Read(); } reader.MoveToContent(); if (reader.NodeType == XmlNodeType.Element) { while (reader.NodeType == XmlNodeType.Element) { result += reader.ReadOuterXml(); reader.MoveToContent(); } } else { result = whiteSpace; result += reader.ReadContentAsString(); } reader.ReadEndElement(); return result; }
/// <summary> /// Reads the <saml:Attribute> element. /// </summary> /// <remarks> /// The default implementation requires that the content of the /// Attribute element be a simple string. To handle complex content /// or content of declared simple types other than xs:string, override /// this method. /// </remarks> /// <param name="reader">An <see cref="XmlReader"/> positioned at a <see cref="Saml2Attribute"/> element.</param> /// <returns>A <see cref="Saml2Attribute"/> instance.</returns> protected virtual Saml2Attribute ReadAttribute(XmlReader reader) { if (null == reader) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader"); } // throw if wrong element if (!reader.IsStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace)) { reader.ReadStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace); } try { Saml2Attribute attribute; bool isEmpty = reader.IsEmptyElement; // @attributes string value; // @xsi:type XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AttributeType, Saml2Constants.Namespace); // @Name - required value = reader.GetAttribute(Saml2Constants.Attributes.Name); if (string.IsNullOrEmpty(value)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Name, Saml2Constants.Elements.Attribute)); } attribute = new Saml2Attribute(value); // @NameFormat - optional value = reader.GetAttribute(Saml2Constants.Attributes.NameFormat); if (!string.IsNullOrEmpty(value)) { if (!UriUtil.CanCreateValidUri(value, UriKind.Absolute)) { throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0011, Saml2Constants.Attributes.Namespace, Saml2Constants.Elements.Action)); } attribute.NameFormat = new Uri(value); } // @FriendlyName - optional attribute.FriendlyName = reader.GetAttribute(Saml2Constants.Attributes.FriendlyName); // @OriginalIssuer - optional. // We are lax on read here, and will accept the following namespaces for original issuer, in order: // http://schemas.xmlsoap.org/ws/2009/09/identity/claims // http://schemas.microsoft.com/ws/2008/06/identity string originalIssuer = reader.GetAttribute(Saml2Constants.Attributes.OriginalIssuer, ClaimType2009Namespace); if (originalIssuer == null) { originalIssuer = reader.GetAttribute(Saml2Constants.Attributes.OriginalIssuer, ProductConstants.NamespaceUri); } if (originalIssuer == String.Empty) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4252))); } attribute.OriginalIssuer = originalIssuer; // content reader.Read(); if (!isEmpty) { while (reader.IsStartElement(Saml2Constants.Elements.AttributeValue, Saml2Constants.Namespace)) { bool isEmptyValue = reader.IsEmptyElement; bool isNil = XmlUtil.IsNil(reader); // FIP 9570 - ENTERPRISE SCENARIO: Saml11SecurityTokenHandler.ReadAttribute is not checking the AttributeValue XSI type correctly. // Lax on receive. If we dont find the AttributeValueXsiType in the format we are looking for in the xml, we default to string. // Read the xsi:type. We are expecting a value of the form "some-non-empty-string" or "some-non-empty-local-prefix:some-non-empty-string". // ":some-non-empty-string" and "some-non-empty-string:" are edge-cases where defaulting to string is reasonable. // For attributeValueXsiTypeSuffix, we want the portion after the local prefix in "some-non-empty-local-prefix:some-non-empty-string" // "some-non-empty-local-prefix:some-non-empty-string" case string attributeValueXsiTypePrefix = null; string attributeValueXsiTypeSuffix = null; string attributeValueXsiTypeSuffixWithLocalPrefix = reader.GetAttribute("type", XmlSchema.InstanceNamespace); if (!string.IsNullOrEmpty(attributeValueXsiTypeSuffixWithLocalPrefix)) { // "some-non-empty-string" case if (attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) == -1) { attributeValueXsiTypePrefix = reader.LookupNamespace(String.Empty); attributeValueXsiTypeSuffix = attributeValueXsiTypeSuffixWithLocalPrefix; } else if (attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) > 0 && attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) < attributeValueXsiTypeSuffixWithLocalPrefix.Length - 1) { string localPrefix = attributeValueXsiTypeSuffixWithLocalPrefix.Substring(0, attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal)); attributeValueXsiTypePrefix = reader.LookupNamespace(localPrefix); attributeValueXsiTypeSuffix = attributeValueXsiTypeSuffixWithLocalPrefix.Substring(attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) + 1); } } if (attributeValueXsiTypePrefix != null && attributeValueXsiTypeSuffix != null) { attribute.AttributeValueXsiType = String.Concat(attributeValueXsiTypePrefix, "#", attributeValueXsiTypeSuffix); } if (isNil) { reader.Read(); if (!isEmptyValue) { reader.ReadEndElement(); } attribute.Values.Add(null); } else if (isEmptyValue) { reader.Read(); attribute.Values.Add(string.Empty); } else { attribute.Values.Add(this.ReadAttributeValue(reader, attribute)); } } reader.ReadEndElement(); } return attribute; } catch (Exception e) { if (System.Runtime.Fx.IsFatal(e)) throw; Exception wrapped = TryWrapReadException(reader, e); if (null == wrapped) { throw; } else { throw wrapped; } } }
/// <summary> /// This method gets called when a special type of Saml2Attribute is detected. The Saml2Attribute passed in /// wraps a Saml2Attribute that contains a collection of AttributeValues, each of which will get mapped to a /// claim. All of the claims will be returned in an ClaimsIdentity with the specified issuer. /// </summary> /// <param name="attribute">The <see cref="Saml2Attribute"/> to use.</param> /// <param name="subject">The <see cref="ClaimsIdentity"/> that is the subject of this token.</param> /// <param name="issuer">The issuer of the claim.</param> /// <exception cref="InvalidOperationException">Will be thrown if the Saml2Attribute does not contain any /// valid Saml2AttributeValues. /// </exception> protected virtual void SetDelegateFromAttribute(Saml2Attribute attribute, ClaimsIdentity subject, string issuer) { // bail here; nothing to add. if (subject == null || attribute == null || attribute.Values == null || attribute.Values.Count < 1) { return; } Saml2Attribute actingAsAttribute = null; Collection<Claim> claims = new Collection<Claim>(); foreach (string attributeValue in attribute.Values) { if (attributeValue != null) { using (XmlDictionaryReader dicReader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(attributeValue), XmlDictionaryReaderQuotas.Max)) { dicReader.MoveToContent(); dicReader.ReadStartElement(Actor); while (dicReader.IsStartElement(Attribute)) { Saml2Attribute innerAttribute = this.ReadAttribute(dicReader); if (innerAttribute != null) { if (innerAttribute.Name == ClaimTypes.Actor) { // In this case, we have two delegates acting as an identity: we do not allow this. if (actingAsAttribute != null) { throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4218)); } actingAsAttribute = innerAttribute; } else { string originalIssuer = innerAttribute.OriginalIssuer; for (int k = 0; k < innerAttribute.Values.Count; ++k) { Claim claim = null; if (string.IsNullOrEmpty(originalIssuer)) { claim = new Claim(innerAttribute.Name, innerAttribute.Values[k], innerAttribute.AttributeValueXsiType, issuer); } else { claim = new Claim(innerAttribute.Name, innerAttribute.Values[k], innerAttribute.AttributeValueXsiType, issuer, originalIssuer); } if (innerAttribute.NameFormat != null) { claim.Properties[ClaimProperties.SamlAttributeNameFormat] = innerAttribute.NameFormat.AbsoluteUri; } if (innerAttribute.FriendlyName != null) { claim.Properties[ClaimProperties.SamlAttributeDisplayName] = innerAttribute.FriendlyName; } claims.Add(claim); } } } } dicReader.ReadEndElement(); // Actor } } } subject.Actor = new ClaimsIdentity(claims, AuthenticationTypes.Federation); this.SetDelegateFromAttribute(actingAsAttribute, subject.Actor, issuer); }
/// <summary> /// Generates a Saml2Attribute from a claim. /// </summary> /// <param name="claim">The <see cref="Claim"/> from which to generate a <see cref="Saml2Attribute"/>.</param> /// <param name="tokenDescriptor">Contains all the information that is used in token issuance.</param> /// <returns>A <see cref="Saml2Attribute"/> based on the claim.</returns> /// <exception cref="ArgumentNullException">The parameter 'claim' is null.</exception> protected virtual Saml2Attribute CreateAttribute(Claim claim, SecurityTokenDescriptor tokenDescriptor) { if (claim == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("claim"); } Saml2Attribute attribute = new Saml2Attribute(claim.Type, claim.Value); if (!StringComparer.Ordinal.Equals(ClaimsIdentity.DefaultIssuer, claim.OriginalIssuer)) { attribute.OriginalIssuer = claim.OriginalIssuer; } attribute.AttributeValueXsiType = claim.ValueType; if (claim.Properties.ContainsKey(ClaimProperties.SamlAttributeNameFormat)) { string nameFormat = claim.Properties[ClaimProperties.SamlAttributeNameFormat]; if (!UriUtil.CanCreateValidUri(nameFormat, UriKind.Absolute)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("nameFormat", SR.GetString(SR.ID0013)); } attribute.NameFormat = new Uri(nameFormat); } if (claim.Properties.ContainsKey(ClaimProperties.SamlAttributeDisplayName)) { attribute.FriendlyName = claim.Properties[ClaimProperties.SamlAttributeDisplayName]; } return attribute; }
private static void CreateIdentityProviderMetadata(SamlIdpData idpData, string fileName, Encoding encoding) { if ( string.IsNullOrEmpty(idpData.SigninCertificateCn)) throw new ApplicationException("no CN for a Certificate supplied"); string signingCertificateSubjectName = idpData.SigninCertificateCn; Constants.NameIdType nidFmt = idpData.NameIdType; MetadataSerializer serializer = new MetadataSerializer(); IdentityProviderSingleSignOnDescriptor item = new IdentityProviderSingleSignOnDescriptor(); EntityDescriptor metadata = new EntityDescriptor(); metadata.EntityId = new EntityId(idpData.EntityId); X509Certificate2 certificate = CertificateHelper.RetrieveCertificate(signingCertificateSubjectName); KeyDescriptor descriptor = new KeyDescriptor( new SecurityKeyIdentifier( new SecurityKeyIdentifierClause[] { new X509SecurityToken(certificate).CreateKeyIdentifierClause<X509RawDataKeyIdentifierClause>() })); descriptor.Use = KeyType.Signing; item.Keys.Add(descriptor); //using 2.0 if (Constants.NameIdType.Saml20 == nidFmt) item.NameIdentifierFormats.Add(Saml2Constants.NameIdentifierFormats.Transient); //using 1.1 if (Constants.NameIdType.Saml11 == nidFmt) item.NameIdentifierFormats.Add(Saml2Constants.NameIdentifierFormats.Unspecified); foreach (var attributeName in idpData.AttributeNames) { Saml2Attribute at1 = new Saml2Attribute(attributeName.Name) { NameFormat = new Uri(Constants.Saml20AttributeNameFormat) }; item.SupportedAttributes.Add(at1); } item.ProtocolsSupported.Add(new Uri(Constants.Saml20Protocol)); item.SingleSignOnServices.Add(new ProtocolEndpoint(new Uri(idpData.BindingType), new Uri(idpData.BindingLocation))); metadata.RoleDescriptors.Add(item); metadata.Contacts.Add(new ContactPerson(ContactType.Technical) { Company = idpData.MainContact.Company, GivenName = idpData.MainContact.GivenName, Surname = idpData.MainContact.SurName, EmailAddresses = { idpData.MainContact.Email }, TelephoneNumbers = { idpData.MainContact.Phone } }); XmlTextWriter writer = new XmlTextWriter(fileName, encoding); serializer.WriteMetadata(writer, metadata); writer.Close(); }
protected override void WriteAttribute(XmlWriter writer, Saml2Attribute data) { if (writer == null) { throw new ArgumentNullException("writer"); } if (data == null) { throw new ArgumentNullException("data"); } // Do not serialize the actor attribute since the Condition/Delegate element has the delegate if ("http://schemas.xmlsoap.org/ws/2009/09/identity/claims/actor" == data.Name) { return; } writer.WriteStartElement("Attribute", "urn:oasis:names:tc:SAML:2.0:assertion"); writer.WriteAttributeString("Name", data.Name); if (null != data.NameFormat) { writer.WriteAttributeString("NameFormat", data.NameFormat.AbsoluteUri); } if (data.FriendlyName != null) { writer.WriteAttributeString("FriendlyName", data.FriendlyName); } if (data.OriginalIssuer != null) { writer.WriteAttributeString("OriginalIssuer", "http://schemas.xmlsoap.org/ws/2009/09/identity/claims", data.OriginalIssuer); } string xmlSchemaNamespace = null; string xmlSchemaType = null; if (!StringComparer.Ordinal.Equals(data.AttributeValueXsiType, "http://www.w3.org/2001/XMLSchema#string")) { int index = data.AttributeValueXsiType.IndexOf('#'); xmlSchemaNamespace = data.AttributeValueXsiType.Substring(0, index); xmlSchemaType = data.AttributeValueXsiType.Substring(index + 1); } foreach (string attributeValue in data.Values) { writer.WriteStartElement("AttributeValue", "urn:oasis:names:tc:SAML:2.0:assertion"); if (attributeValue == null) { writer.WriteAttributeString("nil", "http://www.w3.org/2001/XMLSchema-instance", XmlConvert.ToString(true)); } else if (attributeValue.Length > 0) { if ((xmlSchemaNamespace != null) && (xmlSchemaType != null)) { writer.WriteAttributeString("xmlns", "tn", null, xmlSchemaNamespace); writer.WriteAttributeString("type", "http://www.w3.org/2001/XMLSchema-instance", "tn:" + xmlSchemaType); } else { // GFIPM S2S Appendix A: GFIPM-Specific SAML Assertion Format Rules // Sect 19. <AttributeValue> attributes writer.WriteAttributeString("xmlns", "xs", null, "http://www.w3.org/2001/XMLSchema"); writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance"); writer.WriteAttributeString("xsi", "type", "http://www.w3.org/2001/XMLSchema-instance", "xs:string"); } this.WriteAttributeValue(writer, attributeValue, data); } writer.WriteEndElement(); } writer.WriteEndElement(); }
/// <summary> /// Creates an instance of Saml2AttributeStatement. /// </summary> /// <param name="attribute">The <see cref="Saml2Attribute"/> contained in this statement.</param> public Saml2AttributeStatement(Saml2Attribute attribute) : this(new Saml2Attribute[] { attribute }) { }
protected override string ReadAttributeValue(XmlReader reader, Saml2Attribute attribute) { if (attribute.Name != null) { return base.ReadAttributeValue(reader, attribute); } return "empty"; }