Mojo-SAML

 view release on metacpan or  search on metacpan

lib/Mojolicious/Plugin/SAML.pm  view on Meta::CPAN


  my $login = $conf->{handle_login} // Carp::croak 'handle_login is required';
  my $key   = Crypt::OpenSSL::RSA->new_private_key(path($conf->{key})->slurp);
  my $cert  = Crypt::OpenSSL::X509->new_from_string(path($conf->{cert})->slurp);
  my $idp   = Mojo::SAML::IdP->new->from($conf->{idp});

  my $location  = $conf->{location};
  my $entity_id = $conf->{entity_id} // $location;

  my $key_info = KeyInfo->new(cert => $cert);
  my $key_desc = KeyDescriptor->new(
    key_info => $key_info,
    use => 'signing',
  );
  my $post = AssertionConsumerService->new(
    index    => 0,
    binding  => 'HTTP-POST',
    location => $location,
  );
  my $redir = AssertionConsumerService->new(
    index    => 1,
    binding  => 'HTTP-Redirect',
    location => $location,
  );
  my $sp = SPSSODescriptor->new(
    key_descriptors => [$key_desc],
    assertion_consumer_services => [$post, $redir],
    nameid_format => [qw/unspecified/],
  );
  my $metadata = EntityDescriptor->new(
    id => 'MOJOSAML_METADATA',
    entity_id => $entity_id,
    descriptors => [$sp],
    insert_signature => Signature->new(key_info => $key_info),
    insert_xml_declaration => 1,
    sign_with_key => $key,
  );
  $plugin->metadata($metadata);

  $app->helper('saml.authn_request' => sub {
    my ($c, %opt) = @_;

    my $binding = $opt{binding} // 'HTTP-Redirect';
    my $passive = $opt{passive} // 0;
    # TODO get "sign" default from idp
    my $sign    = $opt{sign}    // 1;

    my $url = $idp->location_for(SingleSignOnService => $binding);
    my $req = AuthnRequest->new(
      issuer => $entity_id,
      assertion_consumer_service_index => 0,
      is_passive => $passive,
      nameid_policy => NameIDPolicy->new(format => 'unspecified'),
      destination => "$url",
    );

    if ($binding eq 'HTTP-Redirect') {
      $url->query(SAMLRequest => $req->to_string_deflate);

      if ($sign) {
        $url->query({SigAlg => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'});
        $key->use_sha256_hash;
        my $val = $url->query->to_string;
        my $sig = $key->sign($val);
        $url->query({Signature => Mojo::Util::b64_encode($sig, '')});
      }

      $c->redirect_to($url);
    } else {
      Carp::croak "Binding '$binding' is not implemented by saml.authn_request helper";
    }
  });

  $app->helper('saml.response' => sub {
    my $c = shift;
    Carp::croak 'saml response methods must be called during a saml response'
      unless my $dom = $c->stash->{'saml.response'};
    return $dom;
  });

  my @ns  = (samlp => 'urn:oasis:names:tc:SAML:2.0:protocol');
  $app->helper('saml.response_success' => sub {
    my $c = shift;
    my $dom = $c->saml->response;
    return !!$dom->at('samlp|Response > samlp|Status > samlp|StatusCode[Value="urn:oasis:names:tc:SAML:2.0:status:Success"]', @ns);
  });

  $app->helper('saml.response_status' => sub {
    my $c = shift;
    my $dom = $c->saml->response;
    return +($dom->at('samlp|Response > samlp|Status > samlp|StatusCode[Value]', @ns) || {})->{Value};
  });

  my $path = Mojo::URL->new($location)->path->to_string;
  my $r = $app->routes->any($path);
  $plugin->route($r);
  Scalar::Util::weaken $plugin->{route};

  $r->get('/descriptor' => sub {
    state $my_meta = $plugin->metadata->to_string; # only render once
    my $c = shift;
    return $c->reply->not_found unless $my_meta;
    $c->render(text => $my_meta, format => 'xml');
  }, 'saml_descriptor');

  $r->get('/authn_request' => sub { shift->saml->authn_request }, 'saml_authn_request');

  $r->any('/' => sub {
    my $c    = shift;
    my $pub  = $idp->public_key_for('signing');
    my $text = Mojo::Util::b64_decode($c->param('SAMLResponse'));
    my $dom  = Mojo::DOM->new->xml(1)->parse($text);
    return $c->reply->exception('Login failed from SAML provider: Response failed to verify')
      unless Mojo::XMLSig::verify($dom, $pub);

    $c->stash->{'saml.response'} = $dom;

    $c->$login;
  }, 'saml_endpoint');

  return $plugin;



( run in 0.724 second using v1.01-cache-2.11-cpan-71847e10f99 )