TT#21819 Add provisioning fields to the API response resource

Remove excrescent code from the pbxdevicemodels API & Panel
Add roles control per method for the API
Use form field "required" attribute for the API uploads
Fix getting old resource for the PATCH and PUT methods

Change-Id: Ia0515b3d071d45435db7c9138ea9de9447775143
changes/25/15525/13
Irina Peshinskaya 8 years ago
parent da60b8c2fd
commit ce315a86c6

@ -1,27 +1,31 @@
package NGCP::Panel::Controller::API::PbxDeviceModels;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::PbxDeviceModels/;
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use Sipwise::Base;
use NGCP::Panel::Utils::Generic qw(:all);
use Data::Dumper;
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::DeviceBootstrap;
use NGCP::Panel::Utils::Device;
require Catalyst::ActionRole::ACL;
require Catalyst::ActionRole::CheckTrailingSlash;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
sub allowed_methods{
return [qw/GET POST OPTIONS HEAD/];
}
__PACKAGE__->set_config();
sub _set_config{
my ($self, $method) = @_;
$method //='';
if ('POST' eq $method){
return {
'ContentType' => ['multipart/form-data'],#,
'Uploads' => [qw/front_image mac_image/],
# Also correct way for the allowed_roles, and really the last word. Will be applied over all others.
# 'AllowedRole' => [qw/admin reseller/],
};
}
return {};
}
# curl -v -X POST --user $USER --insecure -F front_image=@sandbox/spa504g-front.jpg -F mac_image=@sandbox/spa504g-back.jpg -F json='{"reseller_id":1, "vendor":"Cisco", "model":"SPA999", "linerange":[{"name": "Phone Keys", "can_private":true, "can_shared":true, "can_blf":true, "keys":[{"labelpos":"top", "x":5110, "y":5120},{"labelpos":"top", "x":5310, "y":5320}]}]}' https://localhost:4443/api/pbxdevicemodels/
sub api_description {
@ -33,35 +37,17 @@ sub query_params {
{
param => 'reseller_id',
description => 'Filter for models belonging to a certain reseller',
query => {
first => sub {
my $q = shift;
{ reseller_id => $q };
},
second => sub {},
},
query_type => 'string_eq',
},
{
param => 'vendor',
description => 'Filter for vendor matching a vendor name pattern',
query => {
first => sub {
my $q = shift;
{ vendor => $q };
},
second => sub {},
},
query_type => 'string_eq',
},
{
param => 'model',
description => 'Filter for models matching a model name pattern',
query => {
first => sub {
my $q = shift;
{ model => { like => $q } };
},
second => sub {},
},
query_type => 'string_like',
},
];
}
@ -97,251 +83,13 @@ sub documentation_sample {
} ;
}
sub create_item {
my ($self, $c, $resource, $form, $process_extras) = @_;
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::PbxDeviceModels/;
sub resource_name{
return 'pbxdevicemodels';
}
sub dispatch_path{
return '/api/pbxdevicemodels/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-pbxdevicemodels';
}
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller subscriberadmin/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods },
},
);
sub gather_default_action_roles {
my ($self, %args) = @_; my @roles = ();
push @roles, 'NGCP::Panel::Role::HTTPMethods' if $args{attributes}->{Method};
return @roles;
}
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
return 1;
}
sub GET :Allow {
my ($self, $c) = @_;
my $page = $c->request->params->{page} // 1;
my $rows = $c->request->params->{rows} // 10;
{
my $field_devs = $self->item_rs($c);
(my $total_count, $field_devs) = $self->paginate_order_collection($c, $field_devs);
my (@embedded, @links);
for my $dev ($field_devs->all) {
push @embedded, $self->hal_from_item($c, $dev);
push @links, NGCP::Panel::Utils::DataHalLink->new(
relation => 'ngcp:'.$self->resource_name,
href => sprintf('%s%d', $self->dispatch_path, $dev->id),
);
}
push @links,
NGCP::Panel::Utils::DataHalLink->new(
relation => 'curies',
href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
name => 'ngcp',
templated => true,
),
NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf('%s?page=%s&rows=%s', $self->dispatch_path, $page, $rows));
if(($total_count / $rows) > $page ) {
push @links, NGCP::Panel::Utils::DataHalLink->new(relation => 'next', href => sprintf('%s?page=%d&rows=%d', $self->dispatch_path, $page + 1, $rows));
}
if($page > 1) {
push @links, NGCP::Panel::Utils::DataHalLink->new(relation => 'prev', href => sprintf('%s?page=%d&rows=%d', $self->dispatch_path, $page - 1, $rows));
}
my $hal = NGCP::Panel::Utils::DataHal->new(
embedded => [@embedded],
links => [@links],
);
$hal->resource({
total_count => $total_count,
});
my $response = HTTP::Response->new(HTTP_OK, undef,
HTTP::Headers->new($hal->http_headers(skip_links => 1)), $hal->as_json);
$c->response->headers($response->headers);
$c->response->body($response->content);
return;
}
return;
}
sub HEAD :Allow {
my ($self, $c) = @_;
$c->forward(qw(GET));
$c->response->body(q());
return;
}
sub OPTIONS :Allow {
my ($self, $c) = @_;
my $allowed_methods = $self->allowed_methods_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
Accept_Post => 'application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-'.$self->resource_name,
));
$c->response->content_type('application/json');
$c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
return;
}
sub POST :Allow {
my ($self, $c) = @_;
if ($c->user->roles eq 'subscriberadmin') {
$c->log->error("role subscriberadmin cannot create pbxdevicemodels");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid role. Cannot create pbxdevicemodel.");
return;
}
my $guard = $c->model('DB')->txn_scope_guard;
{
last unless $self->forbid_link_header($c);
last unless $self->valid_media_type($c, 'multipart/form-data');
last unless $self->require_wellformed_json($c, 'application/json', $c->req->param('json'));
my $resource = JSON::from_json($c->req->param('json'), { utf8 => 1 });
$resource->{type} //= 'phone';
$resource->{front_image} = $self->get_upload($c, 'front_image');
last unless $resource->{front_image};
# optional, don't set error
$resource->{mac_image} = $c->req->upload('mac_image');
my $form = $self->get_form($c);
last unless $self->validate_form(
c => $c,
resource => $resource,
form => $form,
);
if($c->user->roles eq "admin") {
} elsif($c->user->roles eq "reseller") {
$resource->{reseller_id} = $c->user->reseller_id;
}
my $reseller = $c->model('DB')->resultset('resellers')->find($resource->{reseller_id});
unless($reseller) {
$c->log->error("invalid reseller_id '$$resource{reseller_id}', does not exist");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid reseller_id, does not exist");
last;
}
my $item = NGCP::Panel::Utils::Device::store_and_process_device_model($c, undef, $resource);
my $item;
$item = $c->model('DB')->resultset('autoprov_devices')->find({
reseller_id => $resource->{reseller_id},
vendor => $resource->{vendor},
model => $resource->{model},
});
if($item) {
$c->log->error("device model with vendor '$$resource{vendor}' and model '$$resource{model}'already exists for reseller_id '$$resource{reseller_id}'");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Device model already exists for this reseller");
last;
}
my $linerange = delete $resource->{linerange};
unless(ref $linerange eq "ARRAY") {
$c->log->error("linerange must be array");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid linerange parameter, must be array");
last;
}
my $ft = File::Type->new();
if($resource->{front_image}) {
my $front_image = delete $resource->{front_image};
$resource->{front_image} = $front_image->slurp;
$resource->{front_image_type} = $ft->mime_type($resource->{front_image});
}
if($resource->{mac_image}) {
my $front_image = delete $resource->{mac_image};
$resource->{mac_image} = $front_image->slurp;
$resource->{mac_image_type} = $ft->mime_type($resource->{mac_image});
}
try {
my $connectable_models = delete $resource->{connectable_models};
my $sync_parameters = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_prefetch($c, undef, $resource);
my $credentials = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_credentials_prefetch($c, undef, $resource);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_clear($c, $resource);
$item = $c->model('DB')->resultset('autoprov_devices')->create($resource);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_credentials_store($c, $item, $credentials);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_store($c, $item, $sync_parameters);
NGCP::Panel::Utils::DeviceBootstrap::dispatch_devmod($c, 'register_model', $item);
NGCP::Panel::Utils::Device::process_connectable_models($c, 1, $item, $connectable_models );
foreach my $range(@{ $linerange }) {
unless(ref $range eq "HASH") {
$c->log->error("all elements in linerange must be hashes, but this is " . ref $range . ": " . Dumper $range);
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid range definition inside linerange parameter, all must be hash");
return;
}
foreach my $elem(qw/can_private can_shared can_blf keys/) {
unless(exists $range->{$elem}) {
$c->log->error("missing mandatory attribute '$elem' in a linerange element");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid range definition inside linerange parameter, missing attribute '$elem'");
return;
}
}
unless(ref $range->{keys} eq "ARRAY") {
$c->log->error("linerange.keys must be array");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid linerange.keys parameter, must be array");
last;
}
$range->{num_lines} = @{ $range->{keys} }; # backward compatibility
my $keys = delete $range->{keys};
my $r = $item->autoprov_device_line_ranges->create($range);
my $i = 0;
foreach my $label(@{ $keys }) {
$label->{line_index} = $i++;
unless(ref $label eq "HASH") {
$c->log->error("all elements in linerange must be hashes, but this is " . ref $range . ": " . Dumper $range);
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid range definition inside linerange parameter, all must be hash");
return;
}
$label->{position} = delete $label->{labelpos};
$r->annotations->create($label);
}
}
} catch($e) {
$c->log->error("failed to create device model: $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create device model.");
last;
}
$guard->commit;
$c->response->status(HTTP_CREATED);
$c->response->header(Location => sprintf('/%s%d', $c->request->path, $item->id));
$c->response->body(q());
}
return;
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
return 1;
return $item;
}
1;
# vim: set tabstop=4 expandtab:

@ -1,243 +1,27 @@
package NGCP::Panel::Controller::API::PbxDeviceModelsItem;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use NGCP::Panel::Utils::Generic qw(:all);
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use NGCP::Panel::Utils::ValidateJSON qw();
use NGCP::Panel::Utils::DateTime;
use Path::Tiny qw(path);
use Safe::Isa qw($_isa);
use Clone qw/clone/;
require Catalyst::ActionRole::ACL;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
sub allowed_methods{
return [qw/GET OPTIONS HEAD PATCH PUT/];
}
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::PbxDeviceModels/;
sub resource_name{
return 'pbxdevicemodels';
}
sub dispatch_path{
return '/api/pbxdevicemodels/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-pbxdevicemodels';
}
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller subscriberadmin/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods },
},
);
sub gather_default_action_roles {
my ($self, %args) = @_; my @roles = ();
push @roles, 'NGCP::Panel::Role::HTTPMethods' if $args{attributes}->{Method};
return @roles;
}
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
return 1;
}
sub GET :Allow {
my ($self, $c, $id) = @_;
{
last unless $self->valid_id($c, $id);
my $field_dev = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, devicemodel => $field_dev);
my $hal = $self->hal_from_item($c, $field_dev);
my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
(map { # XXX Data::HAL must be able to generate links with multiple relations
s|rel="(http://purl.org/sipwise/ngcp-api/#rel-resellers)"|rel="item $1"|r
=~ s/rel=self/rel="item self"/r;
} $hal->http_headers),
), $hal->as_json);
$c->response->headers($response->headers);
$c->response->body($response->content);
return;
}
return;
}
sub HEAD :Allow {
my ($self, $c, $id) = @_;
$c->forward(qw(GET));
$c->response->body(q());
return;
}
sub OPTIONS :Allow {
my ($self, $c, $id) = @_;
my $allowed_methods = $self->allowed_methods_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
Accept_Patch => 'application/json-patch+json',
));
$c->response->content_type('application/json');
$c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
return;
}
sub PATCH :Allow {
my ($self, $c, $id) = @_;
if ($c->user->roles eq 'subscriberadmin') {
$c->log->error("role subscriberadmin cannot edit pbxdevicemodel");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid role. Cannot edit pbxdevicemodel.");
return;
}
my $guard = $c->model('DB')->txn_scope_guard;
{
my $preference = $self->require_preference($c);
last unless $preference;
my $json = $self->get_valid_patch_data(
c => $c,
id => $id,
media_type => 'application/json-patch+json',
);
last unless $json;
my $item = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, pbxdevicemodel => $item);
my $old_resource = $self->resource_from_item($c, $item);
#without it error: The entity could not be processed: Modification of a read-only value attempted at /usr/share/perl5/JSON/Pointer.pm line 200, <$fh> line 1.\n
$old_resource = clone($old_resource);
my $resource = $self->apply_patch($c, $old_resource, $json);
last unless $resource;
my $form = $self->get_form($c);
$item = $self->update_item($c, $item, $old_resource, $resource, $form);
last unless $item;
$guard->commit;
if ('minimal' eq $preference) {
$c->response->status(HTTP_NO_CONTENT);
$c->response->header(Preference_Applied => 'return=minimal');
$c->response->body(q());
} else {
my $hal = $self->hal_from_item($c, $item, $form);
my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
$hal->http_headers,
), $hal->as_json);
$c->response->headers($response->headers);
$c->response->header(Preference_Applied => 'return=representation');
$c->response->body($response->content);
}
}
return;
}
sub PUT :Allow {
my ($self, $c, $id) = @_;
if ($c->user->roles eq 'subscriberadmin') {
$c->log->error("role subscriberadmin cannot edit pbxdevicemodel");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid role. Cannot edit pbxdevicemodel.");
return;
}
my $guard = $c->model('DB')->txn_scope_guard;
{
my $preference = $self->require_preference($c);
last unless $preference;
my $item = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, pbxdevicemodel => $item);
last unless $self->forbid_link_header($c);
last unless $self->valid_media_type($c, 'multipart/form-data');
last unless $self->require_wellformed_json($c, 'application/json', $c->req->param('json'));
my $resource = JSON::from_json($c->req->param('json'), { utf8 => 1 });
$resource->{front_image} = $self->get_upload($c, 'front_image');
last unless $resource->{front_image};
# optional, don't set error
$resource->{mac_image} = $c->req->upload('mac_image');
my $old_resource = $self->resource_from_item($c, $item);
my $form = $self->get_form($c);
$item = $self->update_item($c, $item, $old_resource, $resource, $form);
last unless $item;
$guard->commit;
if ('minimal' eq $preference) {
$c->response->status(HTTP_NO_CONTENT);
$c->response->header(Preference_Applied => 'return=minimal');
$c->response->body(q());
} else {
my $hal = $self->hal_from_item($c, $item, $form);
my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
$hal->http_headers,
), $hal->as_json);
$c->response->headers($response->headers);
$c->response->header(Preference_Applied => 'return=representation');
$c->response->body($response->content);
}
}
return;
}
=pod
sub DELETE :Allow {
my ($self, $c, $id) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
{
my $item = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, emailtemplate => $item);
foreach(qw/subscriber_email_template_id passreset_email_template_id invoice_email_template_id/){
$c->model('DB')->resultset('contracts')->search({
$_ => $item->id,
})->update({
$_ => undef,
});
}
$item->delete;
$guard->commit;
$c->response->status(HTTP_NO_CONTENT);
$c->response->body(q());
use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::PbxDeviceModels/;
__PACKAGE__->set_config();
sub _set_config{
my ($self, $method) = @_;
$method //='';
if ('PUT' eq $method || 'PATCH' eq $method){
return {
'ContentType' => ['multipart/form-data'],#,
'Uploads' => [qw/front_image mac_image/],
# Also correct way for the allowed_roles, and really the last word. Will be applied over all others.
# 'AllowedRole' => [qw/admin reseller/],
};
}
return;
return {};
}
=cut
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
return 1;
sub allowed_methods{
return [qw/GET OPTIONS HEAD PATCH PUT/];
}
1;

@ -209,33 +209,13 @@ sub devmod_create :Chained('base') :PathPart('model/create') :Args(0) :Does(ACL)
$form->values->{$_.'_type'} = $ft->mime_type($form->values->{$_});
}
}
my $connectable_models = delete $form->values->{connectable_models};
my $linerange = delete $form->values->{linerange};
my $sync_parameters = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_prefetch($c, undef, $form->values);
my $credentials = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_credentials_prefetch($c, undef, $form->values);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_clear($c, $form->values);
my $devmod = $schema->resultset('autoprov_devices')->create($form->values);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_credentials_store($c, $devmod, $credentials);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_store($c, $devmod, $sync_parameters);
NGCP::Panel::Utils::DeviceBootstrap::dispatch_devmod($c, 'register_model', $devmod);
if(defined $connectable_models) {
NGCP::Panel::Utils::Device::process_connectable_models($c, 1, $devmod, decode_json($connectable_models) );
}
foreach my $range(@{ $linerange }) {
delete $range->{id};
$range->{num_lines} = @{ $range->{keys} }; # backward compatibility
my $keys = delete $range->{keys};
my $r = $devmod->autoprov_device_line_ranges->create($range);
my $i = 0;
foreach my $label(@{ $keys }) {
$label->{line_index} = $i++;
$label->{position} = delete $label->{labelpos};
delete $label->{id};
$r->annotations->create($label);
}
#preparation of the connectable_models before store_and_process_device_model call
if(defined $form->values->{connectable_models}){
$form->values->{connectable_models} = decode_json($form->values->{connectable_models});
}
my $devmod = NGCP::Panel::Utils::Device::store_and_process_device_model($c, undef, $form->values);
delete $c->session->{created_objects}->{reseller};
$c->session->{created_objects}->{device} = { id => $devmod->id };
});
@ -401,76 +381,12 @@ sub devmod_edit :Chained('devmod_base') :PathPart('edit') :Args(0) :Does(ACL) :A
}
}
my $linerange = delete $form->values->{linerange};
my $connectable_models = delete $form->values->{connectable_models};
my $sync_parameters = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_prefetch($c, $c->stash->{devmod}, $form->values);
my $credentials = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_credentials_prefetch($c, $c->stash->{devmod}, $form->values);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_clear($c, $form->values);
$c->stash->{devmod}->update($form->values);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_credentials_store($c, $c->stash->{devmod}, $credentials);
$schema->resultset('autoprov_sync')->search_rs({
device_id => $c->stash->{devmod}->id,
})->delete;
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_store($c, $c->stash->{devmod}, $sync_parameters);
NGCP::Panel::Utils::DeviceBootstrap::dispatch_devmod($c, 'register_model', $c->stash->{devmod} );
if(defined $connectable_models) {
NGCP::Panel::Utils::Device::process_connectable_models($c, 0, $c->stash->{devmod}, decode_json($connectable_models) );
#preparation of the connectable_models before store_and_process_device_model call
if(defined $form->values->{connectable_models}){
$form->values->{connectable_models} = decode_json($form->values->{connectable_models});
}
my @existing_range = ();
my $range_rs = $c->stash->{devmod}->autoprov_device_line_ranges;
foreach my $range(@{ $linerange }) {
next unless(defined $range);
my $keys = delete $range->{keys};
$range->{num_lines} = @{ $keys }; # backward compatibility
my $old_range;
if(defined $range->{id}) {
# should be an existing range, do update
$old_range = $range_rs->find($range->{id});
delete $range->{id};
unless($old_range) {
$old_range = $range_rs->create($range);
} else {
# formhandler only passes set check-boxes, so explicitely unset here
$range->{can_private} //= 0;
$range->{can_shared} //= 0;
$range->{can_blf} //= 0;
$old_range->update($range);
}
} else {
# new range
$old_range = $range_rs->create($range);
}
$old_range->annotations->delete;
my $i = 0;
foreach my $label(@{ $keys }) {
next unless(defined $label);
$label->{line_index} = $i++;
$label->{position} = delete $label->{labelpos};
delete $label->{id};
$old_range->annotations->create($label);
}
push @existing_range, $old_range->id; # mark as valid (delete others later)
# delete field device line assignments with are out-of-range or use a
# feature which is not supported anymore after edit
foreach my $fielddev_line($c->model('DB')->resultset('autoprov_field_device_lines')
->search({ linerange_id => $old_range->id })->all) {
if($fielddev_line->key_num >= $old_range->num_lines ||
($fielddev_line->line_type eq 'private' && !$old_range->can_private) ||
($fielddev_line->line_type eq 'shared' && !$old_range->can_shared) ||
($fielddev_line->line_type eq 'blf' && !$old_range->can_blf)) {
NGCP::Panel::Utils::Device::store_and_process_device_model($c, $c->stash->{devmod}, $form->values);
$fielddev_line->delete;
}
}
}
# delete invalid range ids (e.g. removed ones)
$range_rs->search({
id => { 'not in' => \@existing_range },
})->delete_all;
delete $c->session->{created_objects}->{reseller};
});
NGCP::Panel::Utils::Message::info(

@ -73,7 +73,7 @@ sub get_valid_data{
my $ops = $params{ops} // [qw/replace copy/];
return unless $self->require_valid_patch($c, $json, $ops);
}
return unless $self->get_uploads($c, $json, $params{uploads});
return unless $self->get_uploads($c, $json, $params{uploads}, $params{form});
$resource = $json;
}
@ -329,15 +329,17 @@ sub require_uploads {
# returns Catalyst::Request::Upload
sub get_upload {
my ($self, $c, $field) = @_;
my ($self, $c, $field, $required) = @_;
my $upload = $c->req->upload($field);
return $upload if $upload;
$self->error($c, HTTP_BAD_REQUEST, "This request is missing the upload part '$field' in body.");
if($required){
$self->error($c, HTTP_BAD_REQUEST, "This request is missing the upload part '$field' in body.");
}
return;
}
sub get_uploads {
my ($self, $c, $json, $uploads) = @_;
my ($self, $c, $json, $uploads, $form) = @_;
my (@upload_fields, %mime_types);
if(!$uploads || ('ARRAY' ne ref $uploads && 'HASH' ne ref $uploads ) ){
return;
@ -349,7 +351,12 @@ sub get_uploads {
}
my $ft;
foreach my $field (@upload_fields){
$json->{$field} = $self->get_upload($c, $field);
my $required = $form ? $form->field($field)->required : 1;
my $upload = $self->get_upload($c, $field, $required);
if(!$upload && !$required){
next;
}
$json->{$field} = $upload;
if($mime_types{$field}){
$ft //= File::Type->new();
my $mime_type = $ft->mime_type($json->{$field}->slurp);
@ -404,6 +411,40 @@ sub allowed_methods_filtered {
return $self->allowed_methods;
}
}
#
#old: allowed_roles = [qw/admin subscriber /]
#
#from now also possible:
#sub config_allowed_roles {
# return {
# 'Default' => [qw/admin reseller subscriberadmin/],
# #GET will use default
# 'POST' => [qw/admin reseller/],
# 'PUT' => [qw/admin reseller/],
# 'PATCH' => [qw/admin reseller/],
# };
#}
sub get_allowed_roles {
my($self, $method) = @_;
my $roles_config = $self->config_allowed_roles;
my ($allowed_roles_default,$allowed_roles_per_methods);
if('HASH' eq ref $roles_config){
$allowed_roles_default = delete $roles_config->{Default};
$allowed_roles_per_methods = {map {
$_ => $roles_config->{$_} // $allowed_roles_default,
} @{ $self->allowed_methods }};
}else{
$allowed_roles_default = 'ARRAY' eq ref $roles_config ? $roles_config : [$roles_config];
$allowed_roles_per_methods = {map {
$_ => $allowed_roles_default,
} @{ $self->allowed_methods }};
}
return $method ? $allowed_roles_per_methods->{$method} : $allowed_roles_per_methods;
}
# sub allowed_methods {
# my ($self) = @_;

@ -1,52 +1,75 @@
package NGCP::Panel::Role::API::PbxDeviceModels;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent qw/NGCP::Panel::Role::API/;
use parent 'NGCP::Panel::Role::API';
use Sipwise::Base;
use NGCP::Panel::Utils::Generic qw(:all);
use HTTP::Status qw(:constants);
use NGCP::Panel::Form::Device::ModelAPI;
use NGCP::Panel::Utils::Device;
use NGCP::Panel::Utils::DeviceBootstrap;
use Data::Dumper;
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Status qw(:constants);
use JSON qw();
use File::Type;
use Data::Dumper;
use NGCP::Panel::Form::Device::ModelAPI;
use NGCP::Panel::Utils::DeviceBootstrap;
use NGCP::Panel::Utils::Device;
sub item_name{
return 'pbxdevicemodels';
}
sub resource_name{
return 'pbxdevicemodels';
}
sub dispatch_path{
return '/api/pbxdevicemodels/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-pbxdevicemodels';
}
sub config_allowed_roles {
return {
'Default' => [qw/admin reseller subscriberadmin/],
#GET will use default
'POST' => [qw/admin reseller/],
'PUT' => [qw/admin reseller/],
'PATCH' => [qw/admin reseller/],
};
}
sub hal_links{
my($self, $c, $item, $resource, $form) = @_;
return [
NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:pbxdevicefirmwares", href => sprintf("/api/pbxdevicefirmwares/?device_id=%d", $item->id)),
];
}
sub get_form {
my ($self, $c) = @_;
#use_fields_for_input_without_param
return NGCP::Panel::Form::Device::ModelAPI->new(ctx => $c);
return (NGCP::Panel::Form::Device::ModelAPI->new(ctx => $c));
}
sub hal_from_item {
my ($self, $c, $item) = @_;
my $form;
#my $type = 'pbxdevicemodels';
my $hal = NGCP::Panel::Utils::DataHal->new(
links => [
NGCP::Panel::Utils::DataHalLink->new(
relation => 'curies',
href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
name => 'ngcp',
templated => true,
),
NGCP::Panel::Utils::DataHalLink->new(relation => 'collection', href => sprintf("%s", $self->dispatch_path)),
NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)),
NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:pbxdevicefirmwares", href => sprintf("/api/pbxdevicefirmwares/?device_id=%d", $item->id)),
],
relation => 'ngcp:'.$self->resource_name,
);
sub _item_rs {
my ($self, $c) = @_;
my $item_rs = $c->model('DB')->resultset('autoprov_devices')
->search_rs(undef,{ prefetch => {autoprov_device_line_ranges => 'annotations'} });
if ($c->user->roles eq "admin") {
} elsif ($c->user->roles eq "reseller") {
$item_rs = $item_rs->search({ reseller_id => $c->user->reseller_id });
} elsif ($c->user->roles eq "subscriberadmin") {
my $reseller_id = $c->user->contract->contact->reseller_id;
return unless $reseller_id;
$item_rs = $item_rs->search({
reseller_id => $reseller_id,
});
}
my $resource = $self->resource_from_item($c, $item);
$hal->resource($resource);
return $hal;
return $item_rs;
}
sub resource_from_item {
@ -56,12 +79,13 @@ sub resource_from_item {
delete $resource{front_image};
delete $resource{mac_image};
my $form = $self->get_form($c);
my ($form,$form_exceptions) = $self->get_form($c);
return unless $self->validate_form(
c => $c,
form => $form,
resource => \%resource,
run => 0,
exceptions => $form_exceptions,
);
foreach my $field (qw/reseller_id id extensions_num/){
@ -73,6 +97,9 @@ sub resource_from_item {
foreach my $range($item->autoprov_device_line_ranges->all) {
$self->process_range( \%resource, $range );
}
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_prefetch_api($c, $item,\%resource);
if('extension' eq $item->type){
# show possible devices for extension
$resource{connectable_models} = [map {$_->device->id} ($item->autoprov_extension_device_link->all) ];
@ -83,48 +110,47 @@ sub resource_from_item {
my $extension = $extension_link->extension;
push @{$resource{connectable_models}}, $extension->id;
foreach my $range($extension->autoprov_device_line_ranges->all) {
$self->process_range( \%resource, $range, sub { my $r = shift; $r->{extension_range} = $extension->id;} );#
$self->process_range( \%resource, $range, sub {
my $r = shift;
$r->{extension_range} = $extension->id;
} );#
}
}
}
return \%resource;
}
sub _item_rs {
my ($self, $c) = @_;
my $item_rs = $c->model('DB')->resultset('autoprov_devices')
->search_rs(undef,{ prefetch => {autoprov_device_line_ranges => 'annotations'} });
if ($c->user->roles eq "admin") {
} elsif ($c->user->roles eq "reseller") {
$item_rs = $item_rs->search({ reseller_id => $c->user->reseller_id });
} elsif ($c->user->roles eq "subscriberadmin") {
my $reseller_id = $c->user->contract->contact->reseller_id;
return unless $reseller_id;
$item_rs = $item_rs->search({
reseller_id => $reseller_id,
});
}
return $item_rs;
}
sub item_by_id {
my ($self, $c, $id) = @_;
sub process_form_resource{
my($self,$c, $item, $old_resource, $resource, $form, $process_extras) = @_;
my $item_rs = $self->item_rs($c);
return $item_rs->find($id);
}
sub update_item {
my ($self, $c, $item, $old_resource, $resource, $form) = @_;
$form //= $self->get_form($c);
$resource->{type} //= 'phone';
my $reseller_id = delete $resource->{reseller_id};
if($c->user->roles eq "admin") {
} elsif($c->user->roles eq "reseller") {
$resource->{reseller_id} = $c->user->reseller_id;
$reseller_id = $c->user->reseller_id;
}
$resource->{reseller_id} = $reseller_id;
my $ft = File::Type->new();
if($resource->{front_image}) {
my $front_image = delete $resource->{front_image};
$resource->{front_image} = $front_image->slurp;
$resource->{front_image_type} = $ft->mime_type($resource->{front_image});
}
if($resource->{mac_image}) {
my $front_image = delete $resource->{mac_image};
$resource->{mac_image} = $front_image->slurp;
$resource->{mac_image_type} = $ft->mime_type($resource->{mac_image});
}
return $resource;
}
sub check_resource{
my($self, $c, $item, $old_resource, $resource, $form, $process_extras) = @_;
my $schema = $c->model('DB');
my $reseller = $c->model('DB')->resultset('resellers')->find($resource->{reseller_id});
unless($reseller) {
$c->log->error("invalid reseller_id '".((defined $resource->{reseller_id})?$resource->{reseller_id} : "undefined")."', does not exist");
@ -132,51 +158,12 @@ sub update_item {
return;
}
my $dup_item;
$dup_item = $c->model('DB')->resultset('autoprov_devices')->find({
reseller_id => $resource->{reseller_id},
vendor => $resource->{vendor},
model => $resource->{model},
});
if($dup_item && $dup_item->id != $item->id) {
$c->log->error("device model with vendor '$$resource{vendor}' and model '$$resource{model}'already exists for reseller_id '$$resource{reseller_id}'");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Device model already exists for this reseller");
return;
}
my $linerange = delete $resource->{linerange};
my $linerange = $resource->{linerange};
unless(ref $linerange eq "ARRAY") {
$c->log->error("linerange must be array");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid linerange parameter, must be array");
return;
}
my $ft = File::Type->new();
if($resource->{front_image}) {
my $front_image = delete $resource->{front_image};
$resource->{front_image} = $front_image->slurp;
$resource->{front_image_type} = $ft->mime_type($resource->{front_image});
}
if($resource->{mac_image}) {
my $front_image = delete $resource->{mac_image};
$resource->{mac_image} = $front_image->slurp;
$resource->{mac_image_type} = $ft->mime_type($resource->{mac_image});
}
my $connectable_models = delete $resource->{connectable_models};
my $sync_parameters = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_prefetch($c, $item, $resource);
my $credentials = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_credentials_prefetch($c, $item, $resource);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_clear($c, $resource);
$item->update($resource);
$c->model('DB')->resultset('autoprov_sync')->search_rs({
device_id => $item->id,
})->delete;
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_credentials_store($c, $item, $credentials);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_store($c, $item, $sync_parameters);
NGCP::Panel::Utils::DeviceBootstrap::dispatch_devmod($c, 'register_model', $item);
NGCP::Panel::Utils::Device::process_connectable_models($c, 0, $item, $connectable_models );
my @existing_range = ();
my $range_rs = $item->autoprov_device_line_ranges;
foreach my $range(@{ $linerange }) {
unless(ref $range eq "HASH") {
@ -195,76 +182,44 @@ sub update_item {
$c->log->error("linerange.keys must be array");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid linerange.keys parameter, must be array");
#last? not next?
last;
}
if(defined $range->{id}) {
my $range_by_id = $c->model('DB')->resultset('autoprov_device_line_ranges')->find($range->{id});
if( $range_by_id && ( $range_by_id->device_id != $item->id ) ){
#this is extension linerange, stop processing this linerange completely
#we should care about it here due to backward compatibility, so API user still can make GET => PUT without excluding extension ranges
next;
}
}
#/check input section end
$range->{num_lines} = @{ $range->{keys} }; # backward compatibility
my $keys = delete $range->{keys};
my $old_range;
if(defined $range->{id}) {
# should be an existing range, do update
$old_range = $range_rs->find($range->{id});
delete $range->{id};
unless($old_range) {#really this is strange situation
delete $range->{id};
$old_range = $range_rs->create($range);
} else {
# formhandler only passes set check-boxes, so explicitely unset here
$range->{can_private} //= 0;
$range->{can_shared} //= 0;
$range->{can_blf} //= 0;
$old_range->update($range);
}
} else {
# new range
$old_range = $range_rs->create($range);
return;
}
$old_range->annotations->delete;
my $i = 0;
foreach my $label(@{ $keys }) {
foreach my $label(@{ $range->{keys} }) {
unless(ref $label eq "HASH") {
$c->log->error("all elements in linerange must be hashes, but this is " . ref $range . ": " . Dumper $range);
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid range definition inside linerange parameter, all must be hash");
return;
}
$label->{line_index} = $i++;
$label->{position} = delete $label->{labelpos};
$old_range->annotations->create($label);
}
}
return 1;
}
push @existing_range, $old_range->id; # mark as valid (delete others later)
# delete field device line assignments with are out-of-range or use a
# feature which is not supported anymore after edit
foreach my $fielddev_line($c->model('DB')->resultset('autoprov_field_device_lines')
->search({ linerange_id => $old_range->id })->all) {
if($fielddev_line->key_num >= $old_range->num_lines ||
($fielddev_line->line_type eq 'private' && !$old_range->can_private) ||
($fielddev_line->line_type eq 'shared' && !$old_range->can_shared) ||
($fielddev_line->line_type eq 'blf' && !$old_range->can_blf)) {
sub check_duplicate{
my($self, $c, $item, $old_resource, $resource, $form, $process_extras) = @_;
$fielddev_line->delete;
}
}
my $schema = $c->model('DB');
my $existing_item = $c->model('DB')->resultset('autoprov_devices')->find({
reseller_id => $resource->{reseller_id},
vendor => $resource->{vendor},
model => $resource->{model},
});
if($existing_item && (!$item || $item->id != $existing_item->id)) {
$c->log->error("device model with vendor '$$resource{vendor}' and model '$$resource{model}'already exists for reseller_id '$$resource{reseller_id}'");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Device model already exists for this reseller");
return;
}
# delete invalid range ids (e.g. removed ones)
$range_rs->search({
id => { 'not in' => \@existing_range },
})->delete_all;
return 1;
}
sub update_item_model {
my ($self, $c, $item, $old_resource, $resource, $form, $process_extras) = @_;
NGCP::Panel::Utils::Device::store_and_process_device_model($c, $item, $resource);
return $item;
}
sub process_range {
my($self, $resource, $range, $process_range_cb ) = @_;
my $r = { $range->get_inflated_columns };
@ -285,7 +240,7 @@ sub process_range {
}
$r->{num_lines} = @{ $r->{keys} };
( ( defined $process_range_cb ) && ( 'CODE' eq ref $process_range_cb ) ) and $process_range_cb->($r);
push @{ $resource->{linerange} }, $r;
push @{ $resource->{linerange} }, $r;
}
1;

@ -14,13 +14,12 @@ use NGCP::Panel::Utils::DataHalLink qw();
sub set_config {
my $self = shift;
my $allowed_roles = $self->config_allowed_roles;
$allowed_roles = 'ARRAY' eq ref $allowed_roles ? $allowed_roles : [$allowed_roles];
my $allowed_roles_by_methods = $self->get_allowed_roles();
$self->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => $allowed_roles,
AllowedRole => $allowed_roles_by_methods->{$_},
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,
@ -109,16 +108,17 @@ sub post {
my ($c) = @_;
my $guard = $self->get_transaction_control($c);
{
my ($form, $form_exceptions) = $self->get_form($c, 'add');
my $method_config = $self->config->{action}->{POST};
my ($resource) = $self->get_valid_data(
c => $c,
method => 'POST',
media_type => $method_config->{ContentType} // 'application/json',
uploads => $method_config->{Uploads} // [] ,
c => $c,
method => 'POST',
media_type => $method_config->{ContentType} // 'application/json',
uploads => $method_config->{Uploads} // [] ,
form => $form,
);
last unless $resource;
#instead of type parameter get_form can check request method
my ($form, $form_exceptions) = $self->get_form($c, 'add');
if(!$form_exceptions && $form->can('form_exceptions')){
$form_exceptions = $form->form_exceptions;
}

@ -18,13 +18,12 @@ use NGCP::Panel::Utils::ValidateJSON qw();
sub set_config {
my $self = shift;
my $allowed_roles = $self->config_allowed_roles;
$allowed_roles = 'ARRAY' eq ref $allowed_roles ? $allowed_roles : [$allowed_roles];
my $allowed_roles_by_methods = $self->get_allowed_roles();
$self->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => $allowed_roles,
AllowedRole => $allowed_roles_by_methods->{$_},
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,
@ -86,21 +85,25 @@ sub patch {
my $preference = $self->require_preference($c);
last unless $preference;
my ($form, $form_exceptions, $process_extras);
($form, $form_exceptions) = $self->get_form($c, 'edit');
my $json = $self->get_valid_patch_data(
c => $c,
id => $id,
c => $c,
id => $id,
media_type => 'application/json-patch+json',
form => $form,
);
last unless $json;
my $item = $self->item_by_id_valid($c, $id);
last unless $item;
my $old_resource = { $item->get_inflated_columns };
my $old_resource = $self->resource_from_item($c, $item);
#$old_resource = clone($old_resource);
##without it error: The entity could not be processed: Modification of a read-only value attempted at /usr/share/perl5/JSON/Pointer.pm line 200, <$fh> line 1.\n
my $resource = $self->apply_patch($c, $old_resource, $json);
last unless $resource;
my ($form, $form_exceptions, $process_extras);
($item, $form, $form_exceptions, $process_extras) = $self->update_item($c, $item, $old_resource, $resource, $form, $process_extras );
last unless $item;
@ -119,20 +122,25 @@ sub put {
my $preference = $self->require_preference($c);
last unless $preference;
#TODO: MOVE form exceptions to proper forms as property
#$old_resource = clone($old_resource);
##without it error: The entity could not be processed: Modification of a read-only value attempted at /usr/share/perl5/JSON/Pointer.pm line 200, <$fh> line 1.\n
my ($form, $form_exceptions, $process_extras);
($form, $form_exceptions) = $self->get_form($c, 'edit');
my $item = $self->item_by_id_valid($c, $id);
last unless $item;
my $method_config = $self->config->{action}->{PUT};
my ($resource, $data) = $self->get_valid_data(
c => $c,
id => $id,
method => 'PUT',
media_type => $method_config->{ContentType} // 'application/json',
uploads => $method_config->{Uploads} // [] ,
method => 'PUT',
media_type => $method_config->{ContentType} // 'application/json',
uploads => $method_config->{Uploads} // [] ,
form => $form,
);
last unless $resource;
my $old_resource = { $item->get_inflated_columns };
#TODO: MOVE form exceptions to proper forms as property
my ($form, $form_exceptions, $process_extras);
my $old_resource = $self->resource_from_item($c, $item);
($item, $form, $form_exceptions, $process_extras) = $self->update_item($c, $item, $old_resource, $resource, $form, $process_extras );
last unless $item;

@ -9,6 +9,122 @@ use IO::String;
our $denwaip_masterkey = '@newrocktech2007';
our $denwaip_magic_head = "\x40\x40\x40\x24\x24\x40\x40\x40\x40\x40\x40\x24\x24\x40\x40\x40";
sub store_and_process_device_model {
my ($c, $item, $resource) = @_;
my $just_created = $item ? 0 : 1;
#this deletion should be db->create
my $linerange = delete $resource->{linerange};
my $connectable_models = delete $resource->{connectable_models};
my $sync_parameters = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_prefetch($c, $item, $resource);
my $credentials = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_credentials_prefetch($c, $item, $resource);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_clear($c, $resource);
if($item){
$item->update($resource);
$c->model('DB')->resultset('autoprov_sync')->search_rs({
device_id => $item->id,
})->delete;
}else{
$item = $c->model('DB')->resultset('autoprov_devices')->create($resource);
}
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_credentials_store($c, $item, $credentials);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_store($c, $item, $sync_parameters);
NGCP::Panel::Utils::DeviceBootstrap::dispatch_devmod($c, 'register_model', $item);
if(defined $connectable_models) {
NGCP::Panel::Utils::Device::process_connectable_models($c, $just_created, $item, $connectable_models);
}
if($just_created){
NGCP::Panel::Utils::Device::create_device_model_ranges($c, $item, $linerange);
}else{
NGCP::Panel::Utils::Device::update_device_model_ranges($c, $item, $linerange);
}
return $item;
}
sub create_device_model_ranges {
my ($c, $item, $linerange) = @_;
foreach my $range(@{ $linerange }) {
delete $range->{id};
$range->{num_lines} = @{ $range->{keys} }; # backward compatibility
my $keys = delete $range->{keys};
my $r = $item->autoprov_device_line_ranges->create($range);
my $i = 0;
foreach my $label(@{ $keys }) {
$label->{line_index} = $i++;
$label->{position} = delete $label->{labelpos};
delete $label->{id};
$r->annotations->create($label);
}
}
}
sub update_device_model_ranges {
my ($c, $item, $linerange) = @_;
my @existing_range = ();
my $range_rs = $item->autoprov_device_line_ranges;
foreach my $range(@{ $linerange }) {
next unless(defined $range);
if(defined $range->{id}) {
my $range_by_id = $c->model('DB')->resultset('autoprov_device_line_ranges')->find($range->{id});
if( $range_by_id && ( $range_by_id->device_id != $item->id ) ){
#this is extension linerange, stop processing this linerange completely
#we should care about it here due to backward compatibility, so API user still can make GET => PUT without excluding extension ranges
next;
}
}
my $keys = delete $range->{keys};
$range->{num_lines} = @{ $keys }; # backward compatibility
my $range_db;
if(defined $range->{id}) {
# should be an existing range, do update
$range_db = $range_rs->find($range->{id});
delete $range->{id};
unless($range_db) {#really this is strange situation
$range_db = $range_rs->create($range);
} else {
# formhandler only passes set check-boxes, so explicitely unset here
$range->{can_private} //= 0;
$range->{can_shared} //= 0;
$range->{can_blf} //= 0;
$range_db->update($range);
}
} else {
# new range
$range_db = $range_rs->create($range);
}
$range_db->annotations->delete;
my $i = 0;
foreach my $label(@{ $keys }) {
next unless(defined $label);
$label->{line_index} = $i++;
$label->{position} = delete $label->{labelpos};
delete $label->{id};
$range_db->annotations->create($label);
}
push @existing_range, $range_db->id; # mark as valid (delete others later)
# delete field device line assignments with are out-of-range or use a
# feature which is not supported anymore after edit
foreach my $fielddev_line($c->model('DB')->resultset('autoprov_field_device_lines')
->search({ linerange_id => $range_db->id })->all) {
if($fielddev_line->key_num >= $range_db->num_lines ||
($fielddev_line->line_type eq 'private' && !$range_db->can_private) ||
($fielddev_line->line_type eq 'shared' && !$range_db->can_shared) ||
($fielddev_line->line_type eq 'blf' && !$range_db->can_blf)) {
$fielddev_line->delete;
}
}
}
# delete invalid range ids (e.g. removed ones)
$range_rs->search({
id => { 'not in' => \@existing_range },
})->delete_all;
}
sub process_connectable_models{
my ($c, $just_created, $devmod, $connectable_models_in) = @_;
my $schema = $c->model('DB');

@ -17,7 +17,7 @@ sub dispatch{
$c->log->info("skipping '$action', disabled by configuration");
return;
}
my $params = {
%{get_devmod_params($c, $fdev->profile->config->device)},
mac => $fdev->identifier,
@ -32,7 +32,7 @@ sub dispatch_devmod{
$c->log->info("skipping '$action', disabled by configuration");
return;
}
my $params = get_devmod_params($c,$devmod);
return _dispatch($c, $action, $params);
}
@ -41,7 +41,7 @@ sub _dispatch{
my $redirect_processor = get_redirect_processor($params);
my $ret;
if($redirect_processor){
$c->log->debug( "action=$action;" );
$c->log->debug( "action=$action;" );
if($redirect_processor->can($action)){
$ret = $redirect_processor->$action();
}else{
@ -50,13 +50,13 @@ sub _dispatch{
}
$ret = $redirect_processor->redirect_server_call($action);
}
$c->log->debug( "ret=$ret;" );
$c->log->debug( "ret=$ret;" );
}
return $ret;
}
sub get_devmod_params{
my($c, $devmod) = @_;
my $credentials = $devmod->autoprov_redirect_credentials;
my $vcredentials;
if($credentials){
@ -123,6 +123,26 @@ sub devmod_sync_parameters_prefetch{
return \@parameters;
}
sub devmod_sync_parameters_prefetch_api{
my($c,$item,$resource) = @_;
$resource //= {};
my $schema = $c->model('DB');
my $bootstrap_method = $item->get_column('bootstrap_method');
my $bootstrap_params_rs = $schema->resultset('autoprov_sync')->search_rs({
'autoprov_sync_parameters.bootstrap_method' => $bootstrap_method,
'me.device_id' => $item->id,
},{
join => 'autoprov_sync_parameters',
select => [qw/autoprov_sync_parameters.parameter_name me.parameter_value/],
as => [qw/parameter_name parameter_value/]
});
foreach ($bootstrap_params_rs->all){
my $param_name = 'bootstrap_config_'.$bootstrap_method.'_'.$_->get_column('parameter_name');
$resource->{$param_name} = $_->parameter_value;
}
return $resource;
}
sub devmod_sync_credentials_prefetch{
my($c,$devmod,$params) = @_;
my $schema = $c->model('DB');
@ -144,7 +164,7 @@ sub devmod_sync_credentials_store{
});
if(!$credentials_rs->first){
$credentials->{device_id} = $devmod->id;
$schema->resultset('autoprov_redirect_credentials')->create($credentials);
$schema->resultset('autoprov_redirect_credentials')->create($credentials);
}else{
delete $credentials->{device_id};
$credentials_rs->update($credentials);

@ -15,6 +15,8 @@ use URI::Escape;
use Storable;
use File::Grep qw/fgrep/;
use feature 'state';
use Storable;
use File::Temp qw(tempfile);
sub BUILD {
my $self = shift;
@ -576,6 +578,7 @@ sub load_data_from_script{
my $found = 0;
if(-e $collection_file && fgrep { /set_data_from_script/ } $collection_file ){
#dirty hack, part 1. To think about Safe
my ($fh, $filename) = tempfile();
local @ARGV = qw/load_data_only/;
our $data_out;
do $collection_file;

Loading…
Cancel
Save