From 62d08c8caacba2a4b99bd70ae1ab6b7f60538f65 Mon Sep 17 00:00:00 2001 From: Lari Taskula Date: Fri, 28 Aug 2015 14:31:30 +0300 Subject: [PATCH] Bug 14723: (follow-up) Example of handling custom SMS::Send Driver reports This patch presents an example of how SMS::Send driver can be configured to receive reports from your SMS Gateway provider if they support such feature. With Bug 14723 adding a delivery note column to message_queue, you are now able to update the error status provided by your SMS gateway provider to the message. It uses REST API, so make sure to include dependencies. Koha conf gets a new block "smsProviders", where you are able to define your SMS Gateway login data and the report URL that our provider will use. Koha httpd conf gets an example for blocking all traffic to the REST API address for modifying the message status, apart from our provider IPs. Here is a description of the protocol and common actions with the example driver. 1. message_queue gets processed as usual. 2. Letters.pm calls our example driver with phone number, content and message_id (new!). 3. Our example driver sends a POST request to the SMS Gateway provider as follows: - 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 4. We get (hopefully) an OK message from our SMS provider. It will already let us know 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 } 5. When the delivery is complete, our SMS Gateway will contact us via the report URL we provided. 6. Via Swagger definitions, we will use Koha::REST::V1::Messages delivery_report to handle the 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. 7. All done, we have now handled the report given by our SMS Gateway provider :) --- 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 diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm index b2a183b..08a9c7e 100644 --- a/C4/Installer/PerlDependencies.pm +++ b/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; diff --git a/C4/SMS.pm b/C4/SMS.pm index 1cd67b9..6855f80 100644 --- a/C4/SMS.pm +++ b/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 diff --git a/Koha/REST/V1/Messages.pm b/Koha/REST/V1/Messages.pm new file mode 100644 index 0000000..63f0076 --- /dev/null +++ b/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; + diff --git a/SMS/Send/Labyrintti/Driver.pm b/SMS/Send/Labyrintti/Driver.pm new file mode 100644 index 0000000..e5d8d2c --- /dev/null +++ b/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; + diff --git a/api/v1/swagger.json b/api/v1/swagger.json index 38e9541..b786c59 100644 --- a/api/v1/swagger.json +++ b/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": { diff --git a/etc/koha-conf.xml b/etc/koha-conf.xml index 962e031..7127672 100644 --- a/etc/koha-conf.xml +++ b/etc/koha-conf.xml @@ -145,5 +145,17 @@ __PAZPAR2_TOGGLE_XML_POST__ + + + + + + + + + diff --git a/etc/koha-httpd.conf b/etc/koha-httpd.conf index 98ab216..a299941 100644 --- a/etc/koha-httpd.conf +++ b/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 -- 1.9.1