package Lemonldap::NG::Manager::Api::Providers::SamlSp; our $VERSION = '2.0.8'; package Lemonldap::NG::Manager::Api; use 5.10.0; use utf8; use Mouse; extends 'Lemonldap::NG::Manager::Api::Common'; 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; my $samlSp = $self->_getSamlSpByConfKey( $conf, $confKey ); # Check if confKey is defined return $self->sendError( $req, "SAML service Provider '$confKey' not found", 404 ) unless ( defined $samlSp ); 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 ) ); return $self->sendError( $req, 'Invalid input: pattern is missing', 405 ) unless ( defined $pattern ); $self->logger->debug( "[API] Find SAML SPs by confKey regexp $pattern requested"); # Get latest configuration my $conf = $self->_confAcc->getConf; my @samlSps = map { $_ =~ $pattern ? $self->_getSamlSpByConfKey( $conf, $_ ) : () } keys %{ $conf->{samlSPMetaDataXML} }; 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 ) ); return $self->sendError( $req, 'entityId is missing', 405 ) unless ( defined $entityId ); $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 ); return $self->sendError( $req, "SAML service Provider with entityID '$entityId' not found", 404 ) unless ( defined $samlSp ); return $self->sendJSONresponse( $req, $samlSp ); } sub addSamlSp { my ( $self, $req ) = @_; my $add = $req->jsonBodyToObj; return $self->sendError( $req, "Invalid input: " . $req->error, 405 ) unless ($add); return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ) unless ( defined $add->{confKey} ); return $self->sendError( $req, 'Invalid input: metadata is missing', 405 ) unless ( defined $add->{metadata} ); my $entityId = $self->_readSamlSpEntityId( $add->{metadata} ); return $self->sendError( $req, 'Invalid input: entityID is missing in metadata', 405 ) unless ( defined $entityId ); $self->logger->debug( "[API] Add SAML SP with confKey $add->{confKey} and entityID $entityId requested" ); # Get latest configuration my $conf = $self->_confAcc->getConf( { noCache => 1 } ); return $self->sendError( $req, "Invalid input: A SAML SP with confKey $add->{confKey} already exists", 405 ) if ( defined $self->_getSamlSpByConfKey( $conf, $add->{confKey} ) ); return $self->sendError( $req, "Invalid input: A SAML SP with entityID $entityId already exists", 405 ) if ( defined $self->_getSamlSpByEntityId( $conf, $entityId ) ); my $res = $self->_pushSamlSp( $conf, $add->{confKey}, $add, 1 ); return $self->sendError( $req, $res->{msg}, 405 ) unless ( $res->{res} eq 'ok' ); return $self->sendJSONresponse( $req, { message => "Successful operation" } ); } sub replaceSamlSp { my ( $self, $req ) = @_; my $confKey = $req->params('confKey') or return $self->sendError( $req, 'confKey is missing', 400 ); my $replace = $req->jsonBodyToObj; return $self->sendError( $req, "Invalid input: " . $req->error, 405 ) unless ($replace); return $self->sendError( $req, 'Invalid input: metadata is missing', 405 ) unless ( defined $replace->{metadata} ); $self->logger->debug( "[API] SAML SP $confKey configuration replace requested"); # Get latest configuration my $conf = $self->_confAcc->getConf( { noCache => 1 } ); # Return 404 if not found return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ) unless ( defined $self->_getSamlSpByConfKey( $conf, $confKey ) ); # check if new entityId exists already my $res = $self->_isNewSamlSpEntityIdUnique( $conf, $confKey, $replace ); return $self->sendError( $req, $res->{msg}, 405 ) unless ( $res->{res} eq 'ok' ); $res = $self->_pushSamlSp( $conf, $confKey, $replace, 1 ); return $self->sendError( $req, $res->{msg}, 405 ) unless ( $res->{res} eq 'ok' ); return $self->sendJSONresponse( $req, { message => "Successful operation" } ); } sub updateSamlSp { my ( $self, $req ) = @_; my $res; my $confKey = $req->params('confKey') or return $self->sendError( $req, 'confKey is missing', 400 ); my $update = $req->jsonBodyToObj; return $self->sendError( $req, "Invalid input: " . $req->error, 405 ) unless ($update); $self->logger->debug( "[API] SAML SP $confKey configuration update requested"); # Get latest configuration my $conf = $self->_confAcc->getConf( { noCache => 1 } ); my $current = $self->_getSamlSpByConfKey( $conf, $confKey ); # Return 404 if not found return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ) unless ( defined $current ); if ( defined $update->{metadata} ) { # check if new entityId exists already $res = $self->_isNewSamlSpEntityIdUnique( $conf, $confKey, $update ); return $self->sendError( $req, $res->{msg}, 405 ) unless ( $res->{res} eq 'ok' ); } $res = $self->_pushSamlSp( $conf, $confKey, $update, 0 ); return $self->sendError( $req, $res->{msg}, 405 ) unless ( $res->{res} eq 'ok' ); return $self->sendJSONresponse( $req, { message => "Successful operation" } ); } sub deleteSamlSp { my ( $self, $req ) = @_; my $confKey = $req->params('confKey') or return $self->sendError( $req, 'confKey is missing', 400 ); # Get latest configuration my $conf = $self->_confAcc->getConf( { noCache => 1 } ); my $delete = $self->_getSamlSpByConfKey( $conf, $confKey ); # Return 404 if not found return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ) unless ( defined $delete ); delete $conf->{samlSPMetaDataXML}->{$confKey}; delete $conf->{samlSPMetaDataOptions}->{$confKey}; delete $conf->{samlSPMetaDataExportedAttributes}->{$confKey}; # Save configuration $self->_confAcc->saveConf($conf); return $self->sendJSONresponse( $req, { message => "Successful operation" } ); } sub _getSamlSpByConfKey { my ( $self, $conf, $confKey ) = @_; # Check if confKey is defined return undef unless ( defined $conf->{samlSPMetaDataXML}->{$confKey} ); # 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 }; $samlSp->{exportedAttributes}->{$_}->{friendlyName} = $friendly_name if ( defined $friendly_name && $friendly_name ne '' ); $samlSp->{exportedAttributes}->{$_}->{format} = $format if ( defined $format && $format ne '' ); } return $samlSp; } sub _getSamlSpByEntityId { my ( $self, $conf, $entityId ) = @_; foreach ( keys %{ $conf->{samlSPMetaDataXML} } ) { return $self->_getSamlSpByConfKey( $conf, $_ ) if ( $self->_readSamlSpEntityId( $conf->{samlSPMetaDataXML}->{$_}->{samlSPMetaDataXML} ) eq $entityId ); } return undef; } sub _readSamlSpEntityId { my ( $self, $metadata ) = @_; return ( $metadata =~ /entityID=['"](.+?)['"]/ ) ? $1 : undef; } sub _readSamlSpExportedAttributes { my ( $self, $attrs, $mergeWith ) = @_; 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} ) { return { res => "ko", msg => "Exported attribute $_ has no name" } unless ( defined $attrs->{$_}->{name} ); my $mandatory = 0; my $name = $attrs->{$_}->{name}; my $format = ''; my $friendlyName = ''; ( $mandatory, $name, $format, $friendlyName ) = split( /;/, $mergeWith->{$_} ) if ( defined $mergeWith->{$_} ); if ( defined $attrs->{$_}->{mandatory} ) { $mandatory = ( $attrs->{$_}->{mandatory} eq '1' or $attrs->{$_}->{mandatory} eq 'true' ) ? 1 : 0; } if ( defined $attrs->{$_}->{format} ) { $format = $attrs->{$_}->{format}; return { res => "ko", msg => "Exported attribute $_ format does not exist." } unless ( length( grep { /^$format$/ } @{$allowedFormats} ) ); } $friendlyName = $attrs->{$_}->{friendlyName} if ( defined $attrs->{$_}->{friendlyName} ); $mergeWith->{$_} = "$mandatory;$name;$format;$friendlyName"; } return { res => "ok", exportedAttributes => $mergeWith }; } sub _pushSamlSp { my ( $self, $conf, $confKey, $push, $replace ) = @_; if ($replace) { $conf->{samlSPMetaDataXML}->{$confKey} = {}; $conf->{samlSPMetaDataOptions}->{$confKey} = {}; $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = {}; $push->{options} = $self->_setDefaultValues( $push->{options}, 'samlSPMetaDataNode' ); } $conf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML} = $push->{metadata}; if ( defined $push->{options} ) { my $res = $self->_hasAllowedAttributes( $push->{options}, 'samlSPMetaDataNode' ); return $res unless ( $res->{res} eq 'ok' ); foreach ( keys %{ $push->{options} } ) { $conf->{samlSPMetaDataOptions}->{$confKey}->{$_} = $push->{options}->{$_}; } } if ( defined $push->{exportedAttributes} ) { my $res = $self->_readSamlSpExportedAttributes( $push->{exportedAttributes}, $conf->{samlSPMetaDataExportedAttributes}->{$confKey} ); return $res unless ( $res->{res} eq 'ok' ); $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = $res->{exportedAttributes}; } # Save configuration $self->_confAcc->saveConf($conf); return { res => 'ok' }; } sub _isNewSamlSpEntityIdUnique { my ( $self, $conf, $confKey, $newSp ) = @_; my $newEntityId = $self->_readSamlSpEntityId( $newSp->{metadata} ); my $curEntityId = $self->_readSamlSpEntityId( $self->_getSamlSpByConfKey( $conf, $confKey )->{metadata} ); if ( $newEntityId ne $curEntityId ) { return { res => 'ko', msg => "An SAML service provide with entityId '$newEntityId' already exists" } if ( defined $self->_getSamlSpByEntityId( $conf, $newEntityId ) ); } return { res => 'ok' }; } 1;