lemonldap-ng/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm
2019-12-06 10:41:40 +00:00

510 lines
14 KiB
Perl

package Lemonldap::NG::Manager::Api::Providers;
our $VERSION = '2.0.7';
package Lemonldap::NG::Manager::Api;
use Lemonldap::NG::Manager::Build::CTrees;
use Scalar::Util 'weaken';
sub getOidcRpByConfKey {
my ( $self, $req ) = @_;
my $confKey = $req->params('confKey')
or return $self->sendError( $req, 'confKey is missing', 400 );
$self->logger->debug("[API] OIDC RP $confKey configuration requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
my $oidcRp = $self->_getOidcRpByConfKey($conf, $confKey);
# To save configuration
#$self->_confAcc->saveConf( $conf ) ;
# Dump object
#use Data::Dumper; print STDERR Dumper($self);
# Return 404 if not found
unless (defined $oidcRp) {
return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 );
}
return $self->sendJSONresponse(
$req, $oidcRp
);
}
sub findOidcRpByConfKey {
my ( $self, $req ) = @_;
my $pattern = (
defined $req->params('uPattern') ?
$req->params('uPattern') :
( defined $req->params('pattern') ? $req->params('pattern') : undef )
);
unless (defined $pattern) {
return $self->sendError( $req, 'pattern is missing', 400 );
}
$self->logger->debug("[API] Find OIDC RPs by confKey regexp $pattern requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
my @oidcRps;
foreach (
keys %{
$conf->{oidcRPMetaDataOptions}
}
)
{
if ($_ =~ $pattern) {
push @oidcRps, $self->_getOidcRpByConfKey($conf, $_);
}
}
return $self->sendJSONresponse(
$req, [ @oidcRps ]
);
}
sub findOidcRpByClientId {
my ( $self, $req ) = @_;
my $clientId = (
defined $req->params('uClientId') ?
$req->params('uClientId') :
( defined $req->params('clientId') ? $req->params('clientId') : undef )
);
unless (defined $clientId) {
return $self->sendError( $req, 'clientId is missing', 400 );
}
$self->logger->debug("[API] Find OIDC RPs by clientId $clientId requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
my $oidcRp = $self->_getOidcRpByClientId($conf, $clientId);
if (defined $oidcRp) {
return $self->sendJSONresponse($req, $oidcRp);
}
return $self->sendJSONresponse($req, {});
}
sub addOidcRp {
my ( $self, $req) = @_;
my $oidcRp = $req->jsonBodyToObj;
unless (defined $oidcRp->{confKey}) {
return $self->sendError( $req, 'Invalid input: confKey is missing', 405 );
}
unless (defined $oidcRp->{clientId}) {
return $self->sendError( $req, 'Invalid input: clientId is missing', 405 );
}
$self->logger->debug("[API] Add OIDC RP with confKey $oidcRp->{confKey} and clientId $oidcRp->{clientId} requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
if (defined $self->_getOidcRpByConfKey($conf, $oidcRp->{confKey})) {
return $self->sendError( $req, "Invalid input: An OIDC RP with confKey $oidcRp->{confKey} already exists", 405 );
}
if (defined $self->_getOidcRpByClientId($conf, $oidcRp->{clientId})) {
return $self->sendError( $req, "Invalid input: An OIDC RP with clientId $oidcRp->{clientId} already exists", 405 );
}
my $options;
if (defined $oidcRp->{options}) {
$options = $oidcRp->{options};
}
$options->{oidcRPMetaDataOptionsClientID} = $oidcRp->{clientId};
my $res = $self->_hasAllowedAttributes($options, 'oidcRPMetaDataNode', 'oidcRPMetaDataOptions');
unless ($res->{res} eq 'ok') {
return $self->sendError( $req, $res->{message}, 405);
}
$conf->{oidcRPMetaDataOptions}->{$oidcRp->{confKey}} = $options;
if (defined $oidcRp->{exportedVars}) {
if ($self->_isSimpleKeyValueHash($oidcRp->{exportedVars})) {
$conf->{oidcRPMetaDataExportedVars}->{$oidcRp->{confKey}} = $oidcRp->{exportedVars};
} else {
return $self->sendError( $req, "Invalid input: exportedVars is not a hash object with \"key\":\"value\" attributes", 405 );
}
}
if (defined $oidcRp->{extraClaim}) {
if ($self->_isSimpleKeyValueHash($oidcRp->{extraClaim})) {
$conf->{oidcRPMetaDataOptionsExtraClaims}->{$oidcRp->{confKey}} = $oidcRp->{extraClaim};
} else {
return $self->sendError( $req, "Invalid input: extraClaim is not a hash object with \"key\":\"value\" attributes", 405 );
}
}
# Save configuration
$self->_confAcc->saveConf($conf);
return $self->sendJSONresponse($req, "Successful operation");
}
sub _getOidcRpByConfKey {
my ( $self, $conf, $confKey ) = @_;
# Check if confKey is defined
unless ( defined $conf->{oidcRPMetaDataOptions}->{$confKey} ) {
return undef;
}
# Get Client ID
my $clientId = $conf->{oidcRPMetaDataOptions}->{$confKey}
->{oidcRPMetaDataOptionsClientID};
# Get exported vars
my $exportedVars = $conf->{oidcRPMetaDataExportedVars}->{$confKey};
# Get extra claim
my $extraClaim = $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey};
# Get options
my $options = $conf->{oidcRPMetaDataOptions}->{$confKey};
return {
confKey => $confKey,
clientId => $clientId,
exportedVars => $exportedVars,
extraClaim => $extraClaim,
options => $options
};
}
sub _getOidcRpByClientId {
my ( $self, $conf, $clientId ) = @_;
foreach (
keys %{
$conf->{oidcRPMetaDataOptions}
}
)
{
if ($conf->{oidcRPMetaDataOptions}->{$_}->{oidcRPMetaDataOptionsClientID} eq $clientId) {
return $self->_getOidcRpByConfKey($conf, $_)
}
}
return undef;
}
sub getSamlSpByConfKey {
my ( $self, $req ) = @_;
my $confKey = $req->params('confKey')
or return $self->sendError( $req, 'confKey is missing', 400 );
$self->logger->debug("[API] SAML SP $confKey configuration requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
$samlSp = $self->_getSamlSpByConfKey($conf, $confKey);
# Check if confKey is defined
unless (defined $samlSp) {
return $self->sendError( $req, "SAML service Provider '$confKey' not found", 404 );
}
return $self->sendJSONresponse(
$req, $samlSp
);
}
sub findSamlSpByConfKey {
my ( $self, $req ) = @_;
my $pattern = (
defined $req->params('uPattern') ?
$req->params('uPattern') :
( defined $req->params('pattern') ? $req->params('pattern') : undef )
);
unless (defined $pattern) {
return $self->sendError( $req, 'pattern is missing', 400 );
}
$self->logger->debug("[API] Find SAML SPs by confKey regexp $pattern requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
my @samlSps;
foreach (
keys %{
$conf->{samlSPMetaDataXML}
}
)
{
if ($_ =~ $pattern) {
push @samlSps, $self->_getSamlSpByConfKey($conf, $_);
}
}
return $self->sendJSONresponse(
$req, [ @samlSps ]
);
}
sub findSamlSpByEntityId {
my ( $self, $req ) = @_;
my $entityId = (
defined $req->params('uEntityId') ?
$req->params('uEntityId') :
( defined $req->params('entityId') ? $req->params('entityId') : undef )
);
unless (defined $entityId) {
return $self->sendError( $req, 'entityId is missing', 400 );
}
$self->logger->debug("[API] Find SAML SPs by entityId $entityId requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
my $samlSp = $self->_getSamlSpByEntityId($conf, $entityId);
if (defined $samlSp) {
return $self->sendJSONresponse($req, $samlSp);
}
return $self->sendJSONresponse($req, {});
}
sub addSamlSp {
my ( $self, $req) = @_;
my $samlSp = $req->jsonBodyToObj;
unless (defined $samlSp->{confKey}) {
return $self->sendError( $req, 'Invalid input: confKey is missing', 405 );
}
unless (defined $samlSp->{metadata}) {
return $self->sendError( $req, 'Invalid input: metadata is missing', 405 );
}
my $entityId = $self->_readSamlSpEntityId($samlSp->{metadata});
unless (defined $entityId) {
return $self->sendError( $req, 'Invalid input: entityID is missing in metadata', 405 );
}
$self->logger->debug("[API] Add SAML SP with confKey $samlSp->{confKey} and entityID $entityId requested");
# Get latest configuration
my $conf = $self->_confAcc->getConf;
if (defined $self->_getSamlSpByConfKey($conf, $samlSp->{confKey})) {
return $self->sendError( $req, "Invalid input: A SAML SP with confKey $samlSp->{confKey} already exists", 405 );
}
if (defined $self->_getSamlSpByEntityId($conf, $entityId)) {
return $self->sendError( $req, "Invalid input: A SAML SP with entityID $entityId already exists", 405 );
}
$conf->{samlSPMetaDataXML}->{$samlSp->{confKey}}->{samlSPMetaDataXML} = $samlSp->{metadata};
my $options;
if (defined $samlSp->{options}) {
$options = $samlSp->{options};
}
my $res = $self->_hasAllowedAttributes($options, 'samlSPMetaDataNode', 'samlSPMetaDataOptions');
unless ($res->{res} eq 'ok') {
return $self->sendError( $req, $res->{message}, 405);
}
$conf->{samlSPMetaDataOptions}->{$samlSp->{confKey}} = $options;
if (defined $samlSp->{exportedAttributes}) {
my $res = $self->_readSamlSpExportedAttributes($samlSp->{exportedAttributes});
unless ($res->{res} eq 'ok') {
return $self->sendError( $req, $res->{message}, 405);
}
$conf->{samlSPMetaDataExportedAttributes}->{$samlSp->{confKey}} = $res->{exportedAttributes};
}
# Save configuration
$self->_confAcc->saveConf($conf);
return $self->sendJSONresponse($req, "Successful operation");
}
sub _getSamlSpByConfKey {
my ( $self, $conf, $confKey ) = @_;
# Check if confKey is defined
if ( !defined $conf->{samlSPMetaDataXML}->{$confKey} ) {
return undef;
}
# Get metadata
my $metadata = $conf->{samlSPMetaDataXML}->{$confKey}
->{samlSPMetaDataXML};
# Get options
my $options = $conf->{samlSPMetaDataOptions}->{$confKey};
my $samlSp = {
confKey => $confKey,
metadata => $metadata,
exportedAttributes => {},
options => $options
};
# Get exported attributes
foreach (
keys %{
$conf->{samlSPMetaDataExportedAttributes}
->{$confKey}
}
)
{
# Extract fields from exportedAttr value
my ( $mandatory, $name, $format, $friendly_name ) =
split( /;/,
$conf->{samlSPMetaDataExportedAttributes}
->{$confKey}->{$_} );
$mandatory = !!$mandatory ? 'true' : 'false';
$samlSp->{exportedAttributes}->{$_} = {
name => $name,
mandatory => $mandatory
};
if (defined $friendly_name && $friendly_name ne '') {
$samlSp->{exportedAttributes}->{$_}->{friendlyName} = $friendly_name;
}
if (defined $format && $format ne '') {
$samlSp->{exportedAttributes}->{$_}->{format} = $format;
}
}
return $samlSp;
}
sub _getSamlSpByEntityId {
my ( $self, $conf, $entityId ) = @_;
foreach (
keys %{
$conf->{samlSPMetaDataXML}
}
)
{
if ($self->_readSamlSpEntityId($conf->{samlSPMetaDataXML}->{$_}->{samlSPMetaDataXML}) eq $entityId) {
return $self->_getSamlSpByConfKey($conf, $_);
}
}
return undef;
}
sub _readSamlSpEntityId {
my ( $self, $metadata ) = @_;
if ($metadata =~ /entityID=['"](.+?)['"]/) {
return $1;
}
return undef;
}
sub _readSamlSpExportedAttributes {
my ( $self, $attrs ) = @_;
my $exportedAttributes;
my @allowedFormats = [
"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified",
"urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
"urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
];
foreach (keys %{$attrs}) {
unless (defined $attrs->{$_}->{name}) {
return { res => "ko", message => "Exported attribute $_ has no name"};
}
my $mandatory = 0;
if (defined $attrs->{$_}->{mandatory}) {
if ( $attrs->{$_}->{mandatory} eq '1' or $attrs->{$_}->{mandatory} eq 'true' ) {
$mandatory = 1;
}
}
my $format = '';
if (defined $attrs->{$_}->{format}) {
unless (grep {/^$attrs->{$_}->{format}/} @allowedFormats) {
return { res => "ko", message => "Exported attribute $_ format does not exist."};
}
$format = $attrs->{$_}->{format};
}
my $friendlyName = '';
if (defined $attrs->{$_}->{friendlyName}) {
$friendlyName = $attrs->{$_}->{friendlyName};
}
$exportedAttributes->{$_} = "$mandatory;$attrs->{$_}->{name};$format;$friendlyName";
}
return { res => "ok", exportedAttributes => $exportedAttributes };
}
sub _isSimpleKeyValueHash {
my ( $self, $hash) = @_;
if (ref($hash) ne "HASH") {
return 0;
}
foreach (keys %{$hash}) {
if (ref($hash->{$_}) ne '' || ref($_) ne '') {
return 0;
}
}
return 1;
}
sub _hasAllowedAttributes {
my ( $self, $attributes, $node, $title) = @_;
my $mainTree = Lemonldap::NG::Manager::Build::CTrees::cTrees();
my @allowedAttributes = $self->_listAttributes(
grep { $_->{title} eq $title } (grep { ref($_) eq "HASH" } @{$mainTree->{$node}})
);
foreach $attribute (keys %{$attributes}) {
unless (grep {/^$attribute/} @allowedAttributes) {
return {
res => "ko",
message => "Invalid input: Attribute $attribute does not exist."
};
}
}
return { res => "ok" };
}
sub _listAttributes {
my ( $self, $node) = @_;
my @attributes;
foreach $child (@{$node->{nodes}}) {
if (ref($child) eq "HASH"){
push (@attributes, $self->_listAttributes($child));
} else {
push (@attributes, $child);
}
}
return @attributes;
}
1;