Smee Logo

SmeeGen is an extension to Smee that generates new SAML metadata. It's mainly intended to create SAML-format metadata for the non-SAML authentication protocols, CAS and OIDC, that are supported by the Shibboleth IdP, but will also be able to create vanilla SAML metadata too. At present only SAML SPs, CAS clients and OIDC RPs are supported.

Unlike other Smee extensions SmeeGen does not depend on the Smee package and can be installed on its own.

Hex pm API Docs Github Elixir CI

Run in Livebook

Features

  • Create new CAS client records in SAML metadata format
  • Create new OpenID Connect and Oauth RP records in SAML metadata format
  • Create new metadata records for SAML SPs (work in progress)
  • And that's it - this is profoundly uninteresting software unless you need to automate the creation of metadata

Uses

  • Converting OIDC and CAS configurations into SAML format allows the use of many more Shibboleth IdP features:
    • Friendly MDUI names, logos and privacy information
    • Registration information
    • Organization data
    • Metadata-driven configuration
    • Contacts - these are incredibly helpful when managing lots of metadata in a large organization
  • Moving CAS and OIDC services over to an existing Shibboleth IdP can reduce the number of services needed, and reduce costs and risks. SmeeGen can be used to automate the migration of services by converting their configuration into SAML metadata
  • SmeeGen can be combined with a UI to manage metadata for local services.

SmeeGen is not intended to be used for federated services - it's focused on metadata for services within an organization.

Progress

  • [x] OIDC RP metadata
  • [x] CAS client metadata
  • [ ] SAML SP metadata (WIP)
  • [ ] Templates for common software and apps
  • [ ] Comprehensive tests

Examples

Create a very minimal CAS metadata entry

SmeeGen.cas!("https://sp.example.org")

outputs:


<EntityDescriptor entityID="https://sp.example.org" xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
                  xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
                  xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
                  xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi">
  <SPSSODescriptor protocolSupportEnumeration="https://www.apereo.org/cas/protocol">
    <SingleLogoutService Binding="https://www.apereo.org/cas/protocol/logout"
                         Location="urn:mace:shibboleth:profile:CAS:logout"/>
    <AssertionConsumerService Binding="https://www.apereo.org/cas/protocol/login" Location="https//sp.example.org/"
                              index="1"/>
  </SPSSODescriptor>
</EntityDescriptor>

Create a much larger CAS metadata entry



xml = SmeeGen.cas!(
  "https://example.com/cas",
  service_urls: ["https://example.com/secure", "https://example.com/admin"],
  proxy_urls: ["https://whatever.org/proxy"],
  fragment: false,
  id: "1001",
  valid_until: expires_at,
  cache_duration: duration,
  proxy_keys: [cert],
  displayname: "Test XML metadata",
  information_url: "https://example.com/info",
  description: "Just testing things",
  privacy_url: "https://example.com/privacy_policy",
  privacy_urls: %{
    "fr" => "https://example.com/privacy_policy_fr"
  },
  keywords: %{
    "en" => ["one", "two", "something else"]
  },
  logos: [[url: "https://example.com/logo.png", width: 50, height: 50]],
  contacts: [
    [
      company: "Example Ltd",
      email: "security@example.com",
      surname: "Security",
      givenname: "Security",
      phone: "0161 496 0001",
      type: :sirtfi
    ],
    [
      company: "Example Ltd",
      email: "support@example.com",
      surname: "Support",
      givenname: "Online",
      type: :technical
    ]
  ],
  organization: [
    name: "Example",
    url: "https://example.com"
  ],
  ras: true,
  registration_authority: "https://regauth.example.org",
  registration_instant: DateTime.utc_now(),
  registration_policy_url: "https://blah.example.org"
)

outputs:

<EntityDescriptor entityID="https://example.com/cas" id="1001" validUntil="2026-07-29T16:07:46Z" cacheDuration="PT6H"
                  xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
                  xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
                  xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi">
  <Extensions>
    <mdrpi:RegistrationInfo registrationAuthority="https://regauth.example.org"
                            registrationInstant="2026-07-15T16:09:24Z">
      <mdrpi:RegistrationPolicy xml:lang="en">https://blah.example.org</mdrpi:RegistrationPolicy>
    </mdrpi:RegistrationInfo>
    <mdattr:EntityAttributes>
      <saml:Attribute Name="http://macedir.org/entity-category-support"
                      NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
        <saml:AttributeValue>http://refeds.org/category/research-and-scholarship</saml:AttributeValue>
      </saml:Attribute>
    </mdattr:EntityAttributes>
  </Extensions>
  <SPSSODescriptor protocolSupportEnumeration="https://www.apereo.org/cas/protocol">
    <Extensions>
      <mdui:UIInfo>
        <mdui:DisplayName xml:lang="en">Test XML metadata</mdui:DisplayName>
        <mdui:Description xml:lang="en">Just testing things</mdui:Description>
        <mdui:Keywords xml:lang="en">one two something+else</mdui:Keywords>
        <mdui:InformationURL xml:lang="en">https://example.com/info</mdui:InformationURL>
        <mdui:PrivacyStatementURL xml:lang="en">https://example.com/privacy_policy</mdui:PrivacyStatementURL>
        <mdui:PrivacyStatementURL xml:lang="fr">https://example.com/privacy_policy_fr</mdui:PrivacyStatementURL>
        <mdui:Logo xml:lang="en" width="50" height="50">https://example.com/logo.png</mdui:Logo>
      </mdui:UIInfo>
    </Extensions>
    <KeyDescriptor use="signing">
      <ds:KeyInfo>
        <ds:X509Data>
          <ds:X509Certificate>
            MIIDODCCAiCgAwIBAgIJAKpLQTw/WPXCMA0GCSqGSIb3DQEBCwUAMBwxGjAYBgNV
            BAMTEWFscGhhLmV4YW1wbGUub3JnMB4XDTE4MDYxODE2NDE0NVoXDTE4MDcxODE2
            NDE0NVowHDEaMBgGA1UEAxMRYWxwaGEuZXhhbXBsZS5vcmcwggEiMA0GCSqGSIb3
            DQEBAQUAA4IBDwAwggEKAoIBAQDHSzRUcM0WBtAjR3P1vHYkaaATjNKTxbNHn3zS
            3mLnEgukOVFrr+cRByKKUQQb8MIPkuvKrz3lnoCoOwlFMRPigtChjo3UJGTYEMY9
            2SQQr24U6nE/3d2qFaf2PNIW1SinSjxbE1xeT0bdLcTZHUcE2yEfHKFhcgXIJprv
            R1ceBJBvYYnATuPgUxMjq2ks4kXxG0nNlT13QwBfykBv6I1Wkkc06mEvkMzKNtzr
            ayBK1PygVBNVMUQAFn7Tv6c28BtVLFE9SIKj+5ZcpuWkujVNJF1dYdNmfAz3PiuE
            dPt2yl3t2r/v4CP+U8kBlQs6A83xYrA0MsHnUYOrfL3UTWtZAgMBAAGjfTB7MB0G
            A1UdDgQWBBT/5yBm3mXtsYDvz11kTHsPVGeRcDBMBgNVHSMERTBDgBT/5yBm3mXt
            sYDvz11kTHsPVGeRcKEgpB4wHDEaMBgGA1UEAxMRYWxwaGEuZXhhbXBsZS5vcmeC
            CQCqS0E8P1j1wjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAb/o/M
            mt/nSHOfcjnNJS/LpouaewkoWkQn+FaXZOOvHDYhWur+mHVDpjoszUfgrTX2npmL
            e8Q94bHd+cQrJpZFiYRX8l0p7dAH5Q6Ya/AnHuzGeyQ9fXiDMSWcsg2INcWi7oL9
            h9+V3idcSzgAo1b7+ESSToPj7OG8tgjEp2C9jy0IKEwoApuQtRzxD1XHZFBFwwuH
            nIXWxgctJPU1C+1W9b4bkFSyEGz8/HM7D9feDHbn2AKuRgd99aaOY9D59topf2Zg
            t5sUTWWl54eaF5qoXKY/jdl84Tnmo8GeUufCrS0T6YQGI1LTpicPbqf7zHihQTao
            I1TQuJgghwPvPE9x
          </ds:X509Certificate>
        </ds:X509Data>
      </ds:KeyInfo>
    </KeyDescriptor>
    <SingleLogoutService Binding="https://www.apereo.org/cas/protocol/logout"
                         Location="urn:mace:shibboleth:profile:CAS:logout"/>
    <AssertionConsumerService Binding="https://www.apereo.org/cas/protocol/login" Location="https://example.com/secure"
                              index="1"/>
    <AssertionConsumerService Binding="https://www.apereo.org/cas/protocol/login" Location="https://example.com/admin"
                              index="2"/>
    <AssertionConsumerService Binding="https://www.apereo.org/cas/protocol/proxy" Location="https://whatever.org/proxy"
                              index="3"/>
  </SPSSODescriptor>
  <Organization>
    <OrganizationName xml:lang="en">Example</OrganizationName>
    <OrganizationDisplayName xml:lang="en">Example</OrganizationDisplayName>
    <OrganizationURL xml:lang="en">https://example.com</OrganizationURL>
  </Organization>
  <ContactPerson contactType="other" xmlns:remd="https://refeds.org/metadata"
                 remd:contactType="http://refeds.org/metadata/contactType/security">
    <Company>Example Ltd</Company>
    <GivenName>Security</GivenName>
    <SurName>Security</SurName>
    <EmailAddress>mailto:security@example.com</EmailAddress>
    <TelephoneNumber>0161 496 0001</TelephoneNumber>
  </ContactPerson>
  <ContactPerson contactType="technical">
    <Company>Example Ltd</Company>
    <GivenName>Online</GivenName>
    <SurName>Support</SurName>
    <EmailAddress>mailto:support@example.com</EmailAddress>
  </ContactPerson>
</EntityDescriptor>

Validating the resulting XML with Smee

SmeeGen.cas!("https://sp.example.org")
|> Smee.Entity.new()
|> Smee.Entity.validate!()

Installation

If available in Hex, the package can be installed by adding smee_gen to your list of dependencies in mix.exs:

def deps do
  [
    {:smee_gen, "~> 0.1"}
  ]
end

[!WARNING]
Please make sure you validate any metadata produced by SmeeGen before deploying it: this is an early release and it will absolutely contain mistakes and may produce invalid XML.

Alternatives

I normally list other projects that provide similar functionality but in this case I can't think of any. If you know of one please let me know and I will include it here.

Additional Information

SAML metadata standards

Shibboleth's SAML metadata profiles for CAS

Shibboleth's SAML metadata profiles for OIDC

Documentation

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/smee_gen.

Contributing

You can request new features by creating an issue, or submit a pull request with your contribution.

If you are comfortable working with Python but Smee's Elixir code is unfamiliar then this blog post may help: Elixir For Humans Who Know Python

Please do not submit any PRs or issues generated by "AI". This is a slop-free project and all mistakes are carefully hand-crafted by humans.

Copyright (c) 2026 Digital Identity Ltd, UK

SmeeGen is Apache licensed.

Disclaimers

SmeeGen is not endorsed by The Shibboleth Foundation or any of the organizations mentioned within the code or data. The API may change considerably in the first few releases after 0.1.0.