wwauth/Google.Solutions.WWAuth/Data/Saml2/Assertion.cs (88 lines of code) (raw):
//
// Copyright 2022 Google LLC
//
// 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 Google.Solutions.WWAuth.Util;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Text;
using System.Xml;
namespace Google.Solutions.WWAuth.Data.Saml2
{
/// <summary>
/// SAML 2.0 assertion.
/// </summary>
internal class Assertion : ISubjectToken
{
private readonly Saml2SecurityToken token;
public SubjectTokenType Type => SubjectTokenType.Saml2;
public bool IsEncrypted => false;
public string Value { get; }
public string Audience
=> this.token.Assertion?
.Conditions?
.AudienceRestrictions
.EnsureNotNull()
.FirstOrDefault()?
.Audiences?
.EnsureNotNull()
.FirstOrDefault()?
.ToString();
public string Issuer
=> this.token.Assertion.Issuer.Value;
public DateTimeOffset? Expiry
=> this.token.Assertion.Conditions?.NotOnOrAfter;
public IDictionary<string, object> Attributes
{
get
{
var attributes = new Dictionary<string, object>();
//
// Return NameID.
//
if (this.token.Assertion.Subject?.NameId?.Value is var nameId && nameId != null)
{
attributes["assertion.subject"] = nameId;
}
//
// Return all attributes.
//
// NB. Attribute names are typically URLs. Therefore, we have
// to use
//
// assertion.attributes['<name'>]
//
// instead of
//
// assertion.<name>
//
// Treat multi-valued attributes as arrays:
//
// assertion.<name>[<index>]
//
// This is the syntax used by pool provider mappings.
//
foreach (var statement in this.token.Assertion.Statements
.EnsureNotNull()
.OfType<Saml2AttributeStatement>()
.SelectMany(a => a.Attributes))
{
var mangledName = $"assertion.attributes['{statement.Name}']";
if (statement.Values.Count > 1)
{
for (int i = 0; i < statement.Values.Count; i++)
{
attributes[$"{mangledName}[{i}]"] = statement.Values[i];
}
}
else if (statement.Values.Count == 1)
{
attributes[$"{mangledName}"] = statement.Values[0];
}
}
return attributes;
}
}
private static Saml2SecurityToken ParseSaml2SecurityToken(XmlElement xml)
{
using (var reader = new XmlNodeReader(xml))
{
var tokenHandler = new Saml2SecurityTokenHandler()
{
Configuration = new SecurityTokenHandlerConfiguration()
};
return (Saml2SecurityToken)tokenHandler.ReadToken(reader);
}
}
internal Assertion(
Saml2SecurityToken token,
string encodedToken)
{
this.token = token;
this.Value = encodedToken;
}
public Assertion(GenericXmlSecurityToken token)
: this(
ParseSaml2SecurityToken(token.TokenXml),
Convert.ToBase64String(
Encoding.UTF8.GetBytes(
token.TokenXml.OuterXml)))
{
}
}
}