[Bps-public-commit] RT-Extension-NHD branch, master, created. e331ca2f9746ea7e771b93a63bde80fac86c8d06
Ruslan Zakirov
ruz at bestpractical.com
Wed Sep 14 04:00:19 EDT 2011
The branch, master has been created
at e331ca2f9746ea7e771b93a63bde80fac86c8d06 (commit)
- Log -----------------------------------------------------------------
commit e331ca2f9746ea7e771b93a63bde80fac86c8d06
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date: Wed Sep 14 12:00:04 2011 +0400
initial commit
diff --git a/etc/schema.mysql b/etc/schema.mysql
new file mode 100644
index 0000000..cf772fc
--- /dev/null
+++ b/etc/schema.mysql
@@ -0,0 +1,13 @@
+CREATE TABLE NHDAgreements (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ UUID varchar(48) CHARACTER SET ascii NOT NULL,
+ Name varchar(200) NOT NULL,
+ Status varchar(64) CHARACTER SET ascii NOT NULL,
+ Sender varchar(240) CHARACTER SET ascii NOT NULL,
+ Receiver varchar(240) CHARACTER SET ascii NOT NULL,
+ AccessKey varchar(40) CHARACTER SET ascii NOT NULL,
+
+ PRIMARY KEY (id)
+) ENGINE=InnoDB CHARACTER SET utf8;
+
+CREATE UNIQUE INDEX NHDAgreements1 ON NHDAgreements(UUID);
\ No newline at end of file
diff --git a/html/NoAuth/NHD/1.0/agreements/dhandler b/html/NoAuth/NHD/1.0/agreements/dhandler
new file mode 100644
index 0000000..29ace7b
--- /dev/null
+++ b/html/NoAuth/NHD/1.0/agreements/dhandler
@@ -0,0 +1,55 @@
+<%ARGS>
+$uuid => undef
+$name => undef
+$receiver_url => undef
+$sender_url => undef
+$access_key => undef
+$status => undef
+</%ARGS>
+<%INIT>
+my $duuid = $m->dhandler_arg;
+if ( !RT::Extension::NHD->CheckUUID( $duuid ) || ($uuid||'') ne $duuid ) {
+ return RT::Extension::NHD->BadWebRequest('Unprocessable Entity');
+}
+
+my $token = $r->headers_in->{'X-Ticket-Sharing-Token'};
+return RT::Extension::NHD->BadWebRequest('Authorization Required')
+ unless $token;
+
+my $agreement = RT::NHD::Agreement->new( RT->SystemUser );
+$agreement->Load( $uuid );
+return RT::Extension::NHD->BadWebRequest('Not Found')
+ if !$agreement->id && $action ne 'create';
+
+if ( $token ne join ':', ($agreement->UUID || $uuid), ($agreement->AccessKey || $access_key) ) {
+ return RT::Extension::NHD->BadWebRequest('Forbidden')
+}
+
+my $action = RT::Extension::NHD->WebRequestAction;
+return RT::Extension::NHD->BadWebRequest('Unprocessable Entity')
+ unless $action;
+
+if ( $agreement->id ) {
+ if ( $action eq 'show' ) {
+ return RT::Extension::NHD->WebSendJSON( $agreement->ForJSON );
+ }
+ elsif ( $action eq 'update' ) {
+ my ($status, $msg) = $agreement->UpdateBySender( %{ $agreement->FromJSON( \%ARGS ) } );
+ unless ( $status ) {
+ RT->Logger->error("Couldn't update NHD agreement: $msg");
+ return RT::Extension::NHD->BadWebRequest('Unprocessable Entity');
+ }
+ return RT::Extension::NHD->GoodWebRequest;
+ }
+ else {
+ }
+}
+else {
+ my ($status, $msg) = $agreement->Create( %{ $agreement->FromJSON( \%ARGS ) } );
+ unless ( $status ) {
+ RT->Logger->error("Couldn't create NHD agreement: $msg");
+ return RT::Extension::NHD->BadWebRequest('Unprocessable Entity');
+ }
+ return RT::Extension::NHD->GoodWebRequest('Created');
+}
+</%INIT>
\ No newline at end of file
diff --git a/html/NoAuth/NHD/1.0/autohandler b/html/NoAuth/NHD/1.0/autohandler
new file mode 100644
index 0000000..12b603c
--- /dev/null
+++ b/html/NoAuth/NHD/1.0/autohandler
@@ -0,0 +1,18 @@
+<%INIT>
+my $version = $r->headers_in->{'X-Ticket-Sharing-Version'};
+if ( $version || $version ne '1' ) {
+ return RT::Extension::NHD->BadWebRequest('Precondition Failed');
+}
+
+my $ct = $r->content_type;
+my $method = uc $r->method;
+
+my $data;
+if ( $method ne 'GET' && (!$ct || $ct =~ m{^text/x-json}) ) {
+ $data = RT::Extension::NHD->FromJSON( $r->content );
+ return RT::Extension::NHD->BadWebRequest unless $data;
+}
+
+$m->call_next( %$data );
+
+</%INIT>
diff --git a/lib/RT/Extension/NHD.pm b/lib/RT/Extension/NHD.pm
new file mode 100644
index 0000000..12cad3d
--- /dev/null
+++ b/lib/RT/Extension/NHD.pm
@@ -0,0 +1,88 @@
+use 5.008003;
+use strict;
+use warnings;
+
+package RT::Extension::NHD;
+our $VERSION = '0.01';
+
+=head1 NAME
+
+RT::Extension::NHD - Networked Help Desk protocol for Request Tracker
+
+=head1 DESCRIPTION
+
+=cut
+
+use RT::NHD::Agreement;
+
+sub CheckUUID {
+ my $self = shift;
+ my $value = shift;
+
+ return 0 unless $value && $value =~ /^[0-9a-f]{40}$/i;
+ return 1;
+}
+
+#XXX
+my %HTTP_CODE = (
+ 'OK' => 200,
+ 'Created' => 201,
+
+ 'Bad Request' => 400,
+ 'Precondition Failed' => 400,
+ 'Unprocessable Entity' => 400,
+ 'Not Found' => 404,
+ 'Forbidden' => 400,
+ 'Authorization Required' => 400,
+
+);
+
+sub BadWebRequest {
+ my $self = shift;
+ my $info = shift || 'Bad Request';
+ return $self->StopWebRequest( $info );
+}
+
+sub GoodWebRequest {
+ my $self = shift;
+ my $info = shift || 'OK';
+ return $self->StopWebRequest( $info );
+}
+
+sub StopWebRequest {
+ my $self = shift;
+ my $info = shift;
+ my $code = $HTTP_CODE{ $info } or die "Bad status $info";
+ $HTML::Mason::Commands::r->headers_out->{'Status'} = "$code $info";
+ if ( $code =~ /^2..$/ ) {
+ $HTML::Mason::Commands::m->abort;
+ } else {
+ $HTML::Mason::Commands::m->clear_and_abort;
+ }
+}
+
+my %METHOD_TO_ACTION = ( GET => 'show', POST => 'create', PUT => 'update' );
+sub WebRequestAction {
+ return $METHOD_TO_ACTION{ uc $HTML::Mason::Commands::r->method };
+}
+
+sub WebSendJSON {
+ my $self = shift;
+ my $data = shift;
+ my $status = shift || 'OK';
+
+ $HTML::Mason::Commands::r->content_type( "text/x-json; charset=UTF-8" );
+ return $self->GoodWebRequest( $status );
+}
+
+=head1 AUTHOR
+
+Ruslan Zakirov E<lt>ruz at bestpractical.comE<gt>
+
+=head1 LICENSE
+
+GPL version 2.
+
+=cut
+
+1;
\ No newline at end of file
diff --git a/lib/RT/Extension/NHD/Test.pm b/lib/RT/Extension/NHD/Test.pm
new file mode 100644
index 0000000..4da47cd
--- /dev/null
+++ b/lib/RT/Extension/NHD/Test.pm
@@ -0,0 +1,25 @@
+use strict;
+use warnings;
+
+### after: use lib qw(@RT_LIB_PATH@);
+use lib qw(/opt/rt4/local/lib /opt/rt4/lib);
+
+package RT::Extension::NHD::Test;
+use base 'RT::Test';
+
+sub import {
+ my $class = shift;
+ my %args = @_;
+
+ $args{'requires'} ||= [];
+ if ( $args{'testing'} ) {
+ unshift @{ $args{'requires'} }, 'RT::Extension::NHD';
+ } else {
+ $args{'testing'} = 'RT::Extension::NHD';
+ }
+
+ $class->SUPER::import( %args );
+ $class->export_to_level(1);
+}
+
+1;
diff --git a/lib/RT/NHD/Agreement.pm b/lib/RT/NHD/Agreement.pm
new file mode 100644
index 0000000..a9f2fde
--- /dev/null
+++ b/lib/RT/NHD/Agreement.pm
@@ -0,0 +1,91 @@
+use strict;
+use warnings;
+
+package RT::NHD::Agreement;
+use base 'RT::Record';
+
+use RT::NHD::Agreements;
+
+our @STATUSES = qw(pending accepted declined inactive);
+
+sub Table {'NHDAgreements'}
+
+sub Load {
+ my $self = shift;
+ my $value = shift;
+ if ( $value && $value =~ /^[0-9a-f]{40}$/i ) {
+ return $self->LoadByCols( @_, UUID => $value );
+ }
+ return $self->SUPER::Load( $value, @_ );
+}
+
+sub ValidateUUID { return RT::Extension::NHD->CheckUUID( $_[1] ) }
+sub ValidateAccessKey { return RT::Extension::NHD->CheckUUID( $_[1] ) }
+
+sub ValidateStatus {
+ my $self = shift;
+ my $value = shift;
+
+ return 0 unless $value;
+ return 0 unless grep $_ eq lc $value, @STATUSES;
+ return 1;
+}
+
+sub ValidateSender { return (shift)->_ValidateURI( @_ ) }
+sub ValidateReceiver { return (shift)->_ValidateURI( @_ ) }
+
+sub _ValidateURI {
+ my $self = shift;
+ my $value = shift;
+
+ return 0 unless $value;
+ return 0 unless URI->new( $value );
+ return 1;
+}
+
+sub FromJSON {
+ my $self = shift;
+ my $args = @_;
+
+ return {
+ UUID => $args->{'uuid'},
+ Name => $args->{'name'},
+ Status => $args->{'status'},
+ Sender => $args->{'sender_url'},
+ Receiver => $args->{'receiver_url'},
+ AccessKey => $args->{'access_key'},
+ };
+}
+
+sub ForJSON {
+ my $self = shift;
+ return {
+ uuid => $self->UUID,
+ name => $self->Name,
+ status => $self->Status,
+ sender_url => $self->Sender,
+ receiver_url => $self->Receiver,
+ access_key => $self->AccessKey,
+ };
+}
+
+sub _CoreAccessible { return {
+ id =>
+ { read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)' },
+ UUID =>
+ {read => 1, write => 0, sql_type => 12, length => 40, is_blob => 0, is_numeric => 0, type => 'varchar(40)', default => ''},
+ Name =>
+ {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
+ Status =>
+ {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
+ Sender =>
+ {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''},
+ Receiver =>
+ {read => 1, write => 1, sql_type => 12, length => 240, is_blob => 0, is_numeric => 0, type => 'varchar(240)', default => ''},
+ AccessKey =>
+ {read => 1, write => 1, sql_type => 12, length => 40, is_blob => 0, is_numeric => 0, type => 'varchar(40)', default => ''},
+} }
+
+RT::Base->_ImportOverlays();
+
+1;
diff --git a/lib/RT/NHD/Agreements.pm b/lib/RT/NHD/Agreements.pm
new file mode 100644
index 0000000..268cc6c
--- /dev/null
+++ b/lib/RT/NHD/Agreements.pm
@@ -0,0 +1,13 @@
+use strict;
+use warnings;
+
+package RT::NHD::Agreements;
+use base 'RT::SearchBuilder';
+
+use RT::NHD::Agreement;
+
+sub Table { 'NHDAgreements' }
+
+RT::Base->_ImportOverlays();
+
+1;
diff --git a/spec_question.txt b/spec_question.txt
new file mode 100644
index 0000000..f85dce9
--- /dev/null
+++ b/spec_question.txt
@@ -0,0 +1,11 @@
+* should it /sharing only, can it be /xxx/sharing
+
+* how to define point of entry, eg reciever URL?
+
+* in section "Creating a Ticket Sharing Agreement" text
+ says "requester MUST include a X-Ticket-Sharing-Token",
+ but example has no such header.
+
+* Why content type of requests and responses is not 'text/x-json'?
+
+* is access_key of agreement mutable?
diff --git a/t/api/agreement.t b/t/api/agreement.t
new file mode 100644
index 0000000..4bb866d
--- /dev/null
+++ b/t/api/agreement.t
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use RT::Extension::NHD::Test tests => 25;
+use Digest::SHA1 qw(sha1_hex);
+
+{
+ my $agreement = RT::NHD::Agreement->new( RT->SystemUser );
+ isa_ok($agreement, 'RT::NHD::Agreement');
+ isa_ok($agreement, 'RT::Record');
+}
+
+my $i;
+
+{
+ my $uuid = sha1_hex( ''. ++$i );
+
+ my $agreement = RT::NHD::Agreement->new( RT->SystemUser );
+ my ($id, $msg) = $agreement->Create(
+ UUID => $uuid,
+ Name => 'Test Company',
+ Status => 'pending',
+ Sender => 'http://hoster.example.com/sharing',
+ Receiver => 'http://rt.example.com/sharing',
+ AccessKey => sha1_hex( ''. ++$i ),
+ );
+ ok($id, "Created an agreement $uuid");
+
+ $agreement = RT::NHD::Agreement->new( RT->SystemUser );
+ $agreement->Load( $uuid );
+ ok( $agreement->id, 'loaded agreement' );
+
+ is( $agreement->UUID, $uuid, 'correct value' );
+ is( $agreement->Name, 'Test Company', 'correct value' );
+ is( $agreement->Status, 'pending', 'correct value' );
+ is( $agreement->Sender, 'http://hoster.example.com/sharing', 'correct value' );
+ is( $agreement->Receiver, 'http://rt.example.com/sharing', 'correct value' );
+ like( $agreement->AccessKey, qr{^[0-9a-f]{40}$}i, 'correct value' );
+}
+
+# bad status
+{
+ my $agreement = RT::NHD::Agreement->new( RT->SystemUser );
+ my $uuid = sha1_hex( ''. ++$i );
+ my ($id, $msg) = $agreement->Create(
+ UUID => $uuid,
+ Name => 'Test Company',
+ Status => 'booo',
+ Sender => 'http://hoster.example.com/sharing',
+ Receiver => 'http://rt.example.com/sharing',
+ AccessKey => sha1_hex( ''. ++$i ),
+ );
+ ok(!$id, "Couldn't create an agreement $uuid: $msg");
+}
+
+# can only be created with pending status
+{
+ my $agreement = RT::NHD::Agreement->new( RT->SystemUser );
+ my $uuid = sha1_hex( ''. ++$i );
+ my ($id, $msg) = $agreement->Create(
+ UUID => $uuid,
+ Name => 'Test Company',
+ Status => 'accepted',
+ Sender => 'http://hoster.example.com/sharing',
+ Receiver => 'http://rt.example.com/sharing',
+ AccessKey => sha1_hex( ''. ++$i ),
+ );
+ ok(!$id, "Couldn't create an agreement $uuid: $msg");
+}
+
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list