@@ -, +, @@ Driver reports - user: username, loaded from koha conf smsProviders->labyrintti - password: password, loaded from koha conf smsProviders->labyrintti - dests: destination phone number - text: content of the message - report: the URL that SMS Gateway provider will update the status of this delivery, loaded from koha conf (with REST API, it should be http://yourhost.com/v1/messages) smsProviders->labyrintti if there was an error with the phone number. If so, we return it to Letters.pm via HASH: { status => 1/0, delivery_note => the message of error/success } report. If the given status is "ERROR" (this is our example provider's protocol), we will update the message status (and add delivery note) in message_queue for the given message_id. --- C4/Installer/PerlDependencies.pm | 5 ++ C4/SMS.pm | 1 + Koha/REST/V1/Messages.pm | 39 +++++++++++ SMS/Send/Labyrintti/Driver.pm | 140 +++++++++++++++++++++++++++++++++++++++ api/v1/swagger.json | 69 ++++++++++++++++++- etc/koha-conf.xml | 12 ++++ etc/koha-httpd.conf | 9 +++ 7 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 Koha/REST/V1/Messages.pm create mode 100644 SMS/Send/Labyrintti/Driver.pm --- a/C4/Installer/PerlDependencies.pm +++ a/C4/Installer/PerlDependencies.pm @@ -787,6 +787,11 @@ our $PERL_DEPS = { 'required' => '0', 'min_ver' => '0.28', }, + 'LWP::Curl' => { + 'usage' => 'Curl', + 'required' => '1', + 'min_ver' => '0.12', + }, }; 1; --- a/C4/SMS.pm +++ a/C4/SMS.pm @@ -87,6 +87,7 @@ sub send_sms { # Send a message $sent = $sender->send_sms( to => $params->{'destination'}, text => $params->{'message'}, + message_id => $params->{'message_id'}, ); }; #We might die because SMS::Send $driver is not defined or the sms-number has a bad format --- a/Koha/REST/V1/Messages.pm +++ a/Koha/REST/V1/Messages.pm @@ -0,0 +1,39 @@ +package Koha::REST::V1::Messages; + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Controller'; + +use C4::Letters; +use Data::Dumper; + + +=head2 delivery_report($c, $args, $cb) + +SMS provider returns us with a report on state of the SMS delivery. +If the delivery is unsuccessful, we will change its status. + +=cut +# SMS provider returns us with a report on state of the SMS delivery. +# +sub delivery_report { + my ($c, $args, $cb) = @_; + + if ($args->{status} eq "ERROR") { + C4::Letters::_set_message_status({ + status => "failed", + message_id => $args->{messagenumber}, + delivery_note => $args->{message} + }); + } + + my $return = { + message=> $args->{messagenumber}, + status => $args->{status} + }; + + $c->$cb($return, 200); + +} +1; + --- a/SMS/Send/Labyrintti/Driver.pm +++ a/SMS/Send/Labyrintti/Driver.pm @@ -0,0 +1,140 @@ +=head IN THIS FILE + +This module extends the SMS::Send::Driver interface + +to implement a driver compatible with the Labyrintti SMS Gateway HTTP interface. + + + +Module parameters are sanitated against injection attacks. + +Labyrintti responds: + + success + +phone-number OK message-count description ++358401234567 OK 1 message accepted for sending + + failure + +phone-number ERROR error-code message-count description +e.g: 12345 ERROR 2 1 message failed: Too short phone number + +=cut + + + + + + + +package SMS::Send::Labyrintti::Driver; + + + +#use Modern::Perl; #Can't use this since SMS::Send uses hash keys starting with _ + + + +use utf8; + + + +use SMS::Send::Driver (); + +use LWP::Curl; +use LWP::UserAgent; + +use URI::Escape; +use C4::Context; +use Encode; + +use vars qw{$VERSION @ISA}; +BEGIN { + $VERSION = '0.06'; + @ISA = 'SMS::Send::Driver'; +} + + +##################################################################### +# Constructor + +sub new { + my $class = shift; + my $params = {@_}; + my $username = $params->{_login} ? $params->{_login} : C4::Context->config('smsProviders')->{'labyrintti'}->{'user'}; + my $password = $params->{_password} ? $params->{_password} : C4::Context->config('smsProviders')->{'labyrintti'}->{'passwd'}; + + if (! defined $username ) { + warn "->send_sms(_login) must be defined!"; + return; + } + if (! defined $password ) { + warn "->send_sms(_password) must be defined!"; + return; + } + + #Prevent injection attack + $self->{_login} =~ s/'//g; + $self->{_password} =~ s/'//g; + + # Create the object + my $self = bless {}, $class; + + $self->{UserAgent} = LWP::UserAgent->new(timeout => 5); + $self->{_login} = $username; + $self->{_password} = $password; + + return $self; +} + +sub send_sms { + my $self = shift; + my $params = {@_}; + my $message = $params->{text}; + my $recipientNumber = $params->{to}; + + if (! defined $message ) { + warn "->send_sms(text) must be defined!"; + return; + } + if (! defined $recipientNumber ) { + warn "->send_sms(to) must be defined!"; + return; + } + + #Prevent injection attack! + $recipientNumber =~ s/'//g; + $message =~ s/(")|(\$\()|(`)/\\"/g; #Sanitate " so it won't break the system( iconv'ed curl command ) + + my $base_url = "https://gw.labyrintti.com:28443/sendsms"; + my $parameters = { + 'user' => $self->{_login}, + 'password' => $self->{_password}, + 'dests' => $recipientNumber, + 'text' => $message, + 'unicode' => 'yes', + 'report' => C4::Context->config('smsProviders')->{'labyrintti'}->{'reportUrl'}."/".$params->{_message_id}."/report" + }; + + my $lwpcurl = LWP::Curl->new(auto_encode => 0); + my $return = $lwpcurl->post($base_url, $parameters); + + + my $delivery_note = $return; + + # remove unneccessary stuff from beginning + if (my $status = ($return =~ m/OK [1-9](\d*)?/)) { + $delivery_note =~ ""; + } else { + $delivery_note =~ s/^(\S+\s*){4}//; + } + + return { + status => $status, + delivery_note => $delivery_note, + }; +} + +1; + --- a/api/v1/swagger.json +++ a/api/v1/swagger.json @@ -78,6 +78,41 @@ ] } } + "/messages/{messagenumber}/report": { + "post": { + "x-mojo-controller": "Koha::REST::V1::Messages", + "operationId": "deliveryReport", + "tags": ["messages"], + "parameters": [ + { + "$ref": "#/parameters/messagenumberPathParam" + }, + { + "$ref": "#/parameters/messageStatusFormParam" + }, + { + "$ref": "#/parameters/messageDeliveryNoteFormParam" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "Response for receiving report", + "schema": { + "$ref": "#/definitions/messageStatus" + } + }, + "404": { + "description": "Message not found", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, }, "definitions": { "borrower": { @@ -108,7 +143,18 @@ "type": "string" } } - } + }, + "messageStatus": { + "type": "object", + "properties": { + "message": { + "description": "Message identification number" + }, + "status": { + "description": "Message delivery status" + } + } + }, }, "parameters": { "borrowernumberPathParam": { @@ -117,6 +163,27 @@ "description": "Internal borrower identifier", "required": "true", "type": "integer" + }, + "messagenumberPathParam": { + "name": "messagenumber", + "in": "path", + "description": "Internal message number", + "required": "true", + "type": "integer" + }, + "messageStatusFormParam": { + "name": "status", + "in": "formData", + "description": "Status of message delivery", + "required": "true", + "type": "string" + }, + "messageDeliveryNoteFormParam": { + "name": "message", + "in": "formData", + "description": "Description of message delivery status", + "required": "true", + "type": "string" } }, "securityDefinitions": { --- a/etc/koha-conf.xml +++ a/etc/koha-conf.xml @@ -145,5 +145,17 @@ __PAZPAR2_TOGGLE_XML_POST__ + + + + + + + + + --- a/etc/koha-httpd.conf +++ a/etc/koha-httpd.conf @@ -39,6 +39,15 @@ ProxyPassReverse /v1/ http://localhost:8081/v1/ ##REVERSE PROXYING OK +# Uncomment the following block to restrict access for changing +# message status via reports to only from certain IPs. +# +# Order Deny,Allow +# Deny from all +# Allow from 101.123.213.10 +# Allow from 101.123.213.11 +# + Options -Indexes --