View | Details | Raw Unified | Return to bug 25965
Collapse All | Expand All

(-)a/Koha/REST/V1/SIPoHTTP/SIPoHTTP.pm (+297 lines)
Line 0 Link Here
1
package Koha::REST::V1::SIPoHTTP::SIPoHTTP;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it under the
6
# terms of the GNU General Public License as published by the Free Software
7
# Foundation; either version 3 of the License, or (at your option) any later
8
# version.
9
#
10
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License along
15
# with Koha; if not, write to the Free Software Foundation, Inc.,
16
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18
use Modern::Perl;
19
20
use Mojo::Base 'Mojolicious::Controller';
21
22
use FindBin qw($Bin);
23
use lib "$Bin";
24
use Koha::Exceptions;
25
use Koha::Logger;
26
use XML::LibXML;
27
use IO::Socket::INET;
28
use IO::Socket qw(AF_INET AF_UNIX SOCK_STREAM SHUT_WR);
29
use Socket qw(:crlf);
30
use Try::Tiny;
31
use Mojo::Log;
32
use File::Basename;
33
use C4::Context;
34
35
use strict;
36
use warnings qw( all );
37
38
my $CONFPATH = dirname( $ENV{'KOHA_CONF'} );
39
my $KOHAPATH = C4::Context->config('intranetdir');
40
41
my $log = Koha::Logger->get();
42
43
#This gets called from REST api
44
sub process {
45
46
	my $c = shift->openapi->valid_input or return;
47
48
	my $body          = $c->req->body;
49
	my $xmlrequest = $body;
50
51
	$log->info("Request received.");
52
53
	my $validation = validateXml( $c, $xmlrequest );
54
55
	if ( $validation != 1 ) {
56
57
		$c->render(
58
			text   => "Invalid Request. XML Validation failed.",
59
			status => 400
60
		);
61
62
		return;
63
	}
64
65
	#process sip here
66
	my ( $login, $password ) = getLogin($xmlrequest);
67
68
	if ( !$login or !$password ) {
69
70
		$log->error("Invalid request. Missing login/pw in XML.");
71
72
		$c->render(
73
			text   => "Invalid request. Missing login/pw in XML.",
74
			status => 400
75
		);
76
		return;
77
	}
78
	my $sipmes = extractSip( $xmlrequest, $c );
79
80
	unless ($sipmes) {
81
82
		$log->error("Invalid request. Missing SIP Request in XML.");
83
84
		$c->render(
85
			text   => "Invalid request. Missing SIP Request in XML.",
86
			status => 400
87
		);
88
		return;
89
	}
90
91
	my ( $siphost, $sipport ) = extractServer( $xmlrequest, $c );
92
93
	unless ( $siphost && $sipport ) {
94
95
		$log->error("No config found for login device. ");
96
97
		return $c->render(
98
			text   => "No config found for login device. ",
99
			status => 400
100
		);
101
102
	}
103
104
	my $sipresponse =
105
	  tradeSip( $login, $password, $siphost, $sipport, $sipmes, $c );
106
107
	#remove carriage return from response (\r)
108
	$sipresponse =~ s/\r//g;
109
110
	my $xmlresponse = buildXml($sipresponse);
111
112
	return try {
113
		$c->render( status => 200, text => $xmlresponse );
114
		$log->info("XML response passed to endpoint.");
115
116
	}
117
	catch {
118
		Koha::Exceptions::rethrow_exception($_);
119
	}
120
}
121
122
sub tradeSip {
123
124
	my ( $login, $password, $host, $port, $command_message, $c ) = @_;
125
126
	my $sipsock = IO::Socket::INET->new(
127
		PeerAddr => $host,
128
		PeerPort => $port,
129
		Proto    => 'tcp'
130
	) or die $log->fatal("Can't create a socket for sipserver. Sipserver down?");
131
132
	$sipsock->autoflush(1);
133
134
	my $loginsip = buildLogin( $login, $password );
135
136
	my $terminator = q{};
137
	$terminator = ( $terminator eq 'CR' ) ? $CR : $CRLF;
138
139
	# Set perl to expect the same record terminator it is sending
140
	$/ = $terminator;
141
142
	$log->info("Trying login: $loginsip");
143
144
	my $respdata;
145
146
	print $sipsock $loginsip . $terminator;
147
148
	$sipsock->recv( $respdata, 1024 );
149
	$sipsock->flush;
150
151
	if ( $respdata == "941" ) {
152
153
		$log->info("Login OK. Sending: $command_message");
154
155
		print $sipsock $command_message . $terminator;
156
157
		$sipsock->recv( $respdata, 1024 );
158
		$sipsock->flush;
159
160
		$sipsock->shutdown(SHUT_WR);
161
		$sipsock->shutdown(SHUT_RDWR);    # we stopped using this socket
162
		$sipsock->close;
163
164
		my $respmes = $respdata;
165
		$respmes =~ s/.{1}$//;
166
167
		$log->info("Received: $respmes");
168
169
		return $respdata;
170
	}
171
172
	chomp $respdata;
173
	$log->error(
174
"Unauthorized login for $login: $respdata. Can't process attached SIP message."
175
	);
176
177
	return $respdata;
178
}
179
180
sub buildLogin {
181
182
	my ( $login, $password ) = @_;
183
184
	my $siptempl = "9300CN<SIPDEVICE>|CO<SIPDEVICEPASS>|CPSIPLOCATION|";
185
	return "9300CN" . shift . "|CO" . shift . "|CPSIP2OHTTP|";
186
}
187
188
sub buildXml {
189
190
	my $responsemessage = shift;
191
192
	my $respxml = '<?xml version="1.0" encoding="UTF-8"?>
193
194
<ns1:sip xsi:schemaLocation="https://koha-suomi.fi/sipschema.xsd" xmlns:ns1="https://koha-suomi.fi/sipschema.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
195
196
           <response><TMPL_VAR_MESSAGE></response>
197
198
</ns1:sip>';
199
200
	$respxml =~ s|<TMPL_VAR_MESSAGE>|$responsemessage|;
201
202
	return $respxml;
203
}
204
205
sub extractSip {
206
207
	my ( $xmlmessage, $c ) = @_;
208
209
	my $parser = XML::LibXML->new();
210
	my $xmldoc = $parser->load_xml( string => $xmlmessage );
211
	my $xc     = XML::LibXML::XPathContext->new( $xmldoc->documentElement() );
212
213
	my ($node) = $xc->findnodes('//request');
214
215
	my $sample = $node->textContent;
216
	return $sample;
217
218
}
219
220
sub getLogin {
221
222
	#Retrieve the self check machine login info from XML
223
	my $xmlmessage = shift;
224
225
	my ( $login, $passw );
226
227
	my $parser = XML::LibXML->new();
228
	my $doc    = $parser->load_xml( string => $xmlmessage );
229
	my $xc     = XML::LibXML::XPathContext->new( $doc->documentElement() );
230
231
	my ($node) = $xc->findnodes('//ns1:sip');
232
233
	try {
234
		$login = $node->getAttribute("login");
235
		$passw = $node->getAttribute("password");
236
237
		return $login, $passw;
238
	}
239
	catch {
240
		return 0;
241
	}
242
243
}
244
245
sub extractServer {
246
247
	my ( $host, $port );
248
249
	my ( $xmlmessage, $c )    = @_;
250
	my ( $term,       $pass ) = getLogin($xmlmessage);
251
252
	my $doc =
253
	  XML::LibXML->load_xml( location => $CONFPATH . '/sip2ohttp-config.xml' );
254
	my $xc = XML::LibXML::XPathContext->new( $doc->documentElement() );
255
256
	my ($node) = $xc->findnodes( '//' . $term );
257
258
	unless ($node) {
259
		$log->error(
260
			"Missing server config parameters for $term in sipdevices.xml");
261
		return 0;
262
	}
263
264
	$host = $node->findvalue('./host');
265
	$port = $node->findvalue('./port');
266
	return $host, $port;
267
268
}
269
270
sub validateXml {
271
272
	#For validating the content of the XML SIP message
273
	my ( $c, $xmlbody ) = @_;
274
	my $parser = XML::LibXML->new();
275
276
	# parse and validate the xml against sipschema
277
	# https://koha-suomi.fi/sipschema.xsd
278
	my $schema =
279
	  XML::LibXML::Schema->new(
280
		location => $KOHAPATH . '/koha-tmpl/sipschema.xsd' );
281
282
	try {
283
284
		my $xmldoc = $parser->load_xml( string => $xmlbody );
285
		$schema->validate($xmldoc);
286
		$log->info("XML Validated OK.");
287
		return 1;
288
	}
289
	catch {
290
		$log->error("Could not validate XML - @_");
291
		return 0;
292
293
	};
294
295
}
296
297
1;
(-)a/api/v1/swagger/definitions.json (+3 lines)
Lines 106-110 Link Here
106
  },
106
  },
107
  "mongolog": {
107
  "mongolog": {
108
    "$ref": "definitions/mongolog.json"
108
    "$ref": "definitions/mongolog.json"
109
  },
110
  "sipmessage": {
111
    "$ref": "definitions/sipmessage.json"
109
  }
112
  }
110
}
113
}
(-)a/api/v1/swagger/definitions/sipmessage.json (+4 lines)
Line 0 Link Here
1
{
2
  "type": "string",
3
  "additionalProperties": true
4
}
(-)a/api/v1/swagger/parameters/sipmessage.json (+9 lines)
Line 0 Link Here
1
{
2
  "sipMessage": {
3
    "name": "request",
4
    "in": "formData",
5
    "allowReserved": true,
6
    "description": "sipmessage",
7
    "required": false
8
  }
9
}
(-)a/api/v1/swagger/paths.json (+3 lines)
Lines 217-221 Link Here
217
  },
217
  },
218
  "/suggestions/{suggestionid}": {
218
  "/suggestions/{suggestionid}": {
219
    "$ref": "paths/suggestions.json#/~1suggestions~1{suggestionid}"
219
    "$ref": "paths/suggestions.json#/~1suggestions~1{suggestionid}"
220
  },
221
  "/sipmessages": {
222
  "$ref": "paths/sipmessages.json#/~1sipmessages"
220
  }
223
  }
221
}
224
}
(-)a/api/v1/swagger/paths/sipmessages.json (+51 lines)
Line 0 Link Here
1
{
2
	"/sipmessages": {
3
		"post": {
4
			"x-mojo-to": "SIPoHTTP::SIPoHTTP#process",
5
			"operationId": "SIPoHTTP",
6
			"tags": [
7
				"sipmessages"
8
			],
9
			"parameters": [
10
				{
11
					"name": "query",
12
					"in": "query",
13
					"description": "sip in XML.",
14
					"type": "string"
15
				}
16
			],
17
			"consumes": [
18
				"application/xml"
19
			],
20
			"produces": [
21
				"application/xml"
22
			],
23
			"responses": {
24
				"200": {
25
					"description": "Sip message ok",
26
					"schema": {
27
						"$ref": "../definitions.json#/sipmessage"
28
					}
29
				},
30
				"400": {
31
					"description": "Missing or wrong parameters",
32
					"schema": {
33
						"$ref": "../definitions.json#/error"
34
					}
35
				},
36
				"500": {
37
					"description": "Internal server error",
38
					"schema": {
39
						"$ref": "../definitions.json#/error"
40
					}
41
				},
42
				"503": {
43
					"description": "Under maintenance",
44
					"schema": {
45
						"$ref": "../definitions.json#/error"
46
					}
47
				}
48
			}
49
		}
50
	}
51
}
(-)a/etc/sip2ohttp-config.xml (+37 lines)
Line 0 Link Here
1
<?xml version='1.0' encoding='UTF-8' standalone="yes" ?>
2
3
<Config>
4
5
	<Sipdevices>
6
	
7
	<!-- Devices' login id's and their sip server hosts:ports to use -->
8
	<!-- Set these for devices using sipohttp(s) -->
9
10
	<!-- Examples: (these device login id's and sip server addresses get searched by SIPoHTTP.pm
11
					when the /sipmessages REST endpoint receives a POST request with the XML in the request body.
12
					The device login id's must match the login id's defined in SIPconfig.xml.
13
					Multiple devices can use/share the same sip server.) -->
14
					
15
	<!-- Uncomment and replace with your config
16
						
17
	  	<SIPDEVICENAME>
18
			<host>127.0.0.1</host>
19
			<port>6009</port>
20
		</SIPDEVICENAME>
21
		<SIPDEVICENAMEB>
22
			<host>127.0.0.1</host>
23
			<port>6009</port>
24
		</SIPDEVICENAMEB>
25
		<sipdevice2>
26
			<host>127.0.0.1</host>
27
			<port>6010</port>
28
		</sipdevice2>
29
	
30
	-->
31
32
	</Sipdevices>
33
34
</Config>
35
36
37
(-)a/koha-tmpl/sipschema.xsd (-1 / +29 lines)
Line 0 Link Here
0
- 
1
<?xml version="1.0" encoding="UTF-8"?>
2
3
<!-- sipschema.xsd  $Revision: 1.0 $ support@koha-suomi.fi -->
4
5
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ns1="https://koha-suomi.fi/sipschema.xsd" targetNamespace="https://koha-suomi.fi/sipschema.xsd" elementFormDefault="unqualified" attributeFormDefault="unqualified">
6
7
  <xs:element name="sip">
8
9
    <xs:complexType>
10
11
      <xs:choice>
12
13
        <xs:element name="request" type="xs:string"/>
14
15
        <xs:element name="response" type="xs:string"/>
16
17
        <xs:element name="error" type="xs:string"/>
18
19
      </xs:choice>
20
21
      <xs:attribute name="login" type="xs:string"/>
22
23
      <xs:attribute name="password" type="xs:string"/>
24
25
    </xs:complexType>
26
27
  </xs:element>
28
29
</xs:schema>

Return to bug 25965