File Coverage

blib/lib/Net/Cisco/FMC/v1.pm
Criterion Covered Total %
statement 32 295 10.8
branch 0 70 0.0
condition 0 12 0.0
subroutine 11 32 34.3
pod 16 16 100.0
total 59 425 13.8


line stmt bran cond sub pod time code
1             package Net::Cisco::FMC::v1;
2             $Net::Cisco::FMC::v1::VERSION = '0.008000';
3             # ABSTRACT: Cisco Firepower Management Center (FMC) API version 1 client library
4              
5 1     1   254902 use 5.024;
  1         12  
6 1     1   6 use utf8;
  1         2  
  1         5  
7 1     1   600 use Moo;
  1         8027  
  1         6  
8 1     1   1621 use feature 'signatures';
  1         3  
  1         168  
9 1     1   604 use Types::Standard qw( ArrayRef Dict Str );
  1         116286  
  1         11  
10 1     1   3253 use Carp qw( croak );
  1         2  
  1         73  
11 1     1   530 use Clone qw( clone );
  1         2561  
  1         66  
12 1     1   538 use Syntax::Keyword::Try;
  1         2397  
  1         6  
13 1     1   571 use Net::Cisco::FMC::v1::Role::ObjectMethods;
  1         3  
  1         44  
14 1     1   655 use JSON qw( decode_json );
  1         10310  
  1         5  
15             # use Data::Dumper::Concise;
16              
17 1     1   156 no warnings "experimental::signatures";
  1         2  
  1         4291  
18              
19              
20              
21             has 'domains' => (
22             is => 'rwp',
23             isa => ArrayRef[Dict[name => Str, uuid => Str]],
24             clearer => 1,
25             );
26              
27              
28             has 'domain_uuid' => (
29             is => 'rw',
30             clearer => 1,
31             );
32              
33             has '_refresh_token' => (
34             is => 'rw',
35             clearer => 1,
36             );
37              
38             with 'Net::Cisco::FMC::v1::Role::REST::Client';
39              
40 0     0     sub _create ($self, $url, $object_data, $query_params = {}, $expected_code = 201) {
  0            
  0            
  0            
  0            
  0            
  0            
41 0           my $params = $self->user_agent->www_form_urlencode( $query_params );
42 0           my $res = $self->post("$url?$params", $object_data);
43 0           my $code = $res->code;
44 0           my $data = $res->data;
45             croak($data->{error}->{messages}[0]->{description})
46 0 0         unless $code == $expected_code;
47 0           return $data;
48             }
49              
50 0     0     sub _list ($self, $url, $query_params = {}) {
  0            
  0            
  0            
  0            
51             # the API only allows 1000 objects at a time
52             # work around that by making multiple API calls
53 0           my $offset = 0;
54 0           my $limit = 1000;
55 0           my $more_data_available = 1;
56 0           my @items;
57 0           while ($more_data_available) {
58 0           my $res = $self->get($url, {
59             offset => $offset,
60             limit => $limit,
61             %$query_params,
62             });
63 0           my $code = $res->code;
64 0           my $data = $res->data;
65              
66             croak($data->{error}->{messages}[0]->{description})
67 0 0         unless $code == 200;
68              
69             push @items, $data->{items}->@*
70 0 0 0       if exists $data->{items} && ref $data->{items} eq 'ARRAY';
71              
72             # check if more data is available
73 0 0         if ($offset + $limit < $data->{paging}->{count}) {
74 0           $more_data_available = 1;
75 0           $offset += $limit;
76             }
77             else {
78 0           $more_data_available = 0;
79             }
80             }
81              
82             # return response similar to FMC API
83 0           return { items => \@items };
84             }
85              
86 0     0     sub _get ($self, $url, $query_params = {}) {
  0            
  0            
  0            
  0            
87 0           my $res = $self->get($url, $query_params);
88 0           my $code = $res->code;
89 0           my $data = $res->data;
90              
91             croak($data->{error}->{messages}[0]->{description})
92 0 0         unless $code == 200;
93              
94 0           return $data;
95             }
96              
97 0     0     sub _update ($self, $url, $object, $object_data) {
  0            
  0            
  0            
  0            
  0            
98 0           my $updated_data = clone($object);
99 0           delete $updated_data->{links};
100 0           delete $updated_data->{metadata};
101 0           delete $updated_data->{error};
102 0           $updated_data = { %$updated_data, %$object_data };
103              
104 0           my $res = $self->put($url, $updated_data);
105 0           my $code = $res->code;
106 0           my $data = $res->data;
107             my $errmsg = ref $data eq 'HASH'
108             ? $data->{error}->{messages}[0]->{description}
109 0 0         : $data;
110 0 0         croak($errmsg)
111             unless $code == 200;
112              
113 0           return $data;
114             }
115              
116 0     0     sub _delete ($self, $url) {
  0            
  0            
  0            
117 0           my $res = $self->delete($url);
118             croak($res->data->{error}->{messages}[0]->{description})
119 0 0         unless $res->code == 200;
120 0           return 1;
121             }
122              
123             Net::Cisco::FMC::v1::Role::ObjectMethods->apply([
124             {
125             path => 'object',
126             object => 'portobjectgroups',
127             singular => 'portobjectgroup',
128             },
129             {
130             path => 'object',
131             object => 'protocolportobjects',
132             singular => 'protocolportobject',
133             },
134             {
135             path => 'object',
136             object => 'icmpv4objects',
137             singular => 'icmpv4object',
138             },
139             {
140             path => 'object',
141             object => 'icmpv6objects',
142             singular => 'icmpv6object',
143             },
144             {
145             path => 'object',
146             object => 'interfacegroups',
147             singular => 'interfacegroup',
148             },
149             {
150             path => 'object',
151             object => 'networkgroups',
152             singular => 'networkgroup',
153             },
154             {
155             path => 'object',
156             object => 'networks',
157             singular => 'network',
158             },
159             {
160             path => 'object',
161             object => 'hosts',
162             singular => 'host',
163             },
164             {
165             path => 'object',
166             object => 'ranges',
167             singular => 'range',
168             },
169             {
170             path => 'object',
171             object => 'fqdns',
172             singular => 'fqdn',
173             },
174             {
175             path => 'object',
176             object => 'securityzones',
177             singular => 'securityzone',
178             },
179             {
180             path => 'object',
181             object => 'slamonitors',
182             singular => 'slamonitor',
183             },
184             {
185             path => 'object',
186             object => 'urlgroups',
187             singular => 'urlgroup',
188             },
189             {
190             path => 'object',
191             object => 'urls',
192             singular => 'url',
193             },
194             {
195             path => 'object',
196             object => 'vlangrouptags',
197             singular => 'vlangrouptag',
198             },
199             {
200             path => 'object',
201             object => 'vlantags',
202             singular => 'vlantag',
203             },
204             {
205             path => 'policy',
206             object => 'accesspolicies',
207             singular => 'accesspolicy',
208             },
209             {
210             path => 'object',
211             object => 'networkaddresses',
212             singular => 'networkaddress',
213             },
214             {
215             path => 'object',
216             object => 'ports',
217             singular => 'port',
218             },
219             {
220             path => 'devices',
221             object => 'devicerecords',
222             singular => 'devicerecord',
223             },
224             {
225             path => 'assignment',
226             object => 'policyassignments',
227             singular => 'policyassignment',
228             },
229             ]);
230              
231              
232 0     0 1   sub login($self) {
  0            
  0            
233 0           my $res = $self->post('/api/fmc_platform/v1/auth/generatetoken', undef,
234             { authentication => 'basic' });
235 0 0         if ($res->code == 204) {
236             # the allowed domains are returned in the domains header JSON
237             # encoded
238 0           my $domains = decode_json($res->response->header('domains'));
239             #say Dumper($domains);
240 0           $self->_set_domains($domains);
241             # set the current domain to the first available
242 0           $self->domain_uuid($domains->[0]->{uuid});
243              
244             # store refresh token
245 0           $self->_refresh_token($res->response->header('x-auth-refresh-token'));
246 0           $self->set_persistent_header('X-auth-access-token',
247             $res->response->header('x-auth-access-token'));
248             }
249             else {
250 0           croak($res->data->{error}->{messages}[0]->{description});
251             }
252             }
253              
254              
255 0     0 1   sub relogin($self) {
  0            
  0            
256 0           my $domain_uuid = $self->domain_uuid;
257 0           $self->login;
258 0 0 0       $self->domain_uuid($domain_uuid)
259             if defined $domain_uuid && $domain_uuid ne '';
260             }
261              
262              
263 0     0 1   sub logout($self) {
  0            
  0            
264 0           my $res = $self->post('/api/fmc_platform/v1/auth/revokeaccess');
265 0 0         if ($res->code == 204) {
266 0           $self->clear_domains;
267 0           $self->clear_domain_uuid;
268 0           $self->_clear_refresh_token;
269 0           $self->clear_persistent_headers;
270             }
271             else {
272 0           croak($res->data->{error}->{messages}[0]->{description});
273             }
274             }
275              
276              
277 0     0 1   sub create_accessrule ($self, $accesspolicy_id, $object_data, $query_params = {}) {
  0            
  0            
  0            
  0            
  0            
278 0           return $self->_create(join('/',
279             '/api/fmc_config/v1/domain',
280             $self->domain_uuid,
281             'policy',
282             'accesspolicies',
283             $accesspolicy_id,
284             'accessrules'
285             ), $object_data, $query_params);
286             }
287              
288              
289 0     0 1   sub list_accessrules ($self, $accesspolicy_id, $query_params = {}) {
  0            
  0            
  0            
  0            
290 0           return $self->_list(join('/',
291             '/api/fmc_config/v1/domain',
292             $self->domain_uuid,
293             'policy',
294             'accesspolicies',
295             $accesspolicy_id,
296             'accessrules'
297             ), $query_params);
298             }
299              
300              
301 0     0 1   sub get_accessrule ($self, $accesspolicy_id, $id, $query_params = {}) {
  0            
  0            
  0            
  0            
  0            
302 0           return $self->_get(join('/',
303             '/api/fmc_config/v1/domain',
304             $self->domain_uuid,
305             'policy',
306             'accesspolicies',
307             $accesspolicy_id,
308             'accessrules',
309             $id
310             ), $query_params);
311             }
312              
313              
314 0     0 1   sub update_accessrule ($self, $accesspolicy_id, $object, $object_data) {
  0            
  0            
  0            
  0            
  0            
315 0           my $id = $object->{id};
316 0           my $fmc_rule = clone($object);
317 0           for my $user ($fmc_rule->{users}->{objects}->@*) {
318 0           delete $user->{realm};
319             }
320 0           return $self->_update(join('/',
321             '/api/fmc_config/v1/domain',
322             $self->domain_uuid,
323             'policy',
324             'accesspolicies',
325             $accesspolicy_id,
326             'accessrules',
327             $id
328             ), $fmc_rule, $object_data);
329             }
330              
331              
332              
333 0     0 1   sub delete_accessrule ($self, $accesspolicy_id, $id) {
  0            
  0            
  0            
  0            
334 0           return $self->_delete(join('/',
335             '/api/fmc_config/v1/domain',
336             $self->domain_uuid,
337             'policy',
338             'accesspolicies',
339             $accesspolicy_id,
340             'accessrules',
341             $id
342             ));
343             }
344              
345              
346 0     0 1   sub list_deployabledevices ($self, $query_params = {}) {
  0            
  0            
  0            
347 0           return $self->_list(join('/',
348             '/api/fmc_config/v1/domain',
349             $self->domain_uuid,
350             'deployment',
351             'deployabledevices'
352             ), $query_params);
353             }
354              
355              
356 0     0 1   sub create_deploymentrequest ($self, $object_data) {
  0            
  0            
  0            
357 0           my $data = $self->_create(join('/',
358             '/api/fmc_config/v1/domain',
359             $self->domain_uuid,
360             'deployment',
361             'deploymentrequests'
362             ), $object_data, {}, 202);
363 0           return $data;
364             }
365              
366              
367 0     0 1   sub get_task ($self, $id) {
  0            
  0            
  0            
368 0           return $self->_get(join('/',
369             '/api/fmc_config/v1/domain',
370             $self->domain_uuid,
371             'job',
372             'taskstatuses',
373             $id
374             ));
375             }
376              
377              
378 0     0 1   sub wait_for_task ($self, $id, $callback) {
  0            
  0            
  0            
  0            
379 0 0         croak "id missing"
380             unless defined $id;
381 0 0 0       croak "callback must be a coderef"
382             if defined $callback && ref $callback ne 'CODE';
383              
384 0           my %in_progress_status_for_type = (
385             DEVICE_DEPLOYMENT => 'Deploying',
386             );
387              
388 0           my $task = $self->get_task($id);
389             die "support for task type '$task->{taskType}' not implemented\n"
390 0 0         unless exists $in_progress_status_for_type{$task->{taskType}};
391             do {
392 0 0         &$callback($task)
393             if defined $callback;
394 0           sleep 1;
395 0           $task = $self->get_task($id);
396             } until (
397 0           $task->{status} ne $in_progress_status_for_type{$task->{taskType}});
398 0           return $task;
399             }
400              
401              
402 0     0 1   sub cleanup_protocolport ($self, $portobj) {
  0            
  0            
  0            
403             #say Dumper($rule);
404             #say "protocolport: " . Dumper($portobj);
405 0           my $protocolportobject = $self->get_protocolportobject($portobj->{id});
406             #say Dumper($protocolportobject);
407 0           my $new_name = lc($protocolportobject->{protocol});
408 0 0         if ( exists $protocolportobject->{port} ) {
409 0           $new_name .= '_' . $protocolportobject->{port};
410             }
411             # avoid 'predefined name' errors
412             else {
413 0           $new_name .= '_any';
414             }
415              
416 0           say "\t", $protocolportobject->{name}, ' ⮕ ', $new_name;
417             try {
418             my $portobject = $self->update_protocolportobject($protocolportobject, { name => $new_name });
419             say "\tname updated";
420             return { %$portobject{qw( id type )} };
421             }
422 0           catch {
423             # replace with existing object
424             if ( $@ =~ /The object name \S+ already exists/ ) {
425             # find existing object
426             my $existing_portobject = $self->find_protocolportobject({ name => $new_name });
427             say "\texisting object used";
428             return { %$existing_portobject{qw( id type )} };
429             }
430             else {
431             croak "name update failed: $@";
432             }
433             }
434             }
435              
436              
437 0     0 1   sub cleanup_icmpv4object ($self, $icmpv4obj) {
  0            
  0            
  0            
438             #say "icmpv4object: " . Dumper($icmpv4obj);
439 0           my $icmpv4object = $self->get_icmpv4object($icmpv4obj->{id});
440             #say Dumper($icmpv4object);
441 0           my $new_name = 'icmp_' . lc($icmpv4object->{icmpType});
442             $new_name .= '_' . $icmpv4object->{code}
443 0 0         if exists $icmpv4object->{code};
444              
445 0           say "\t", $icmpv4object->{name}, ' ⮕ ', $new_name;
446             try {
447             my $obj = $self->update_icmpv4object($icmpv4object, { name => $new_name });
448             say "\tname updated";
449             return { %$obj{qw( id type )} };
450             }
451 0           catch {
452             #say "name update failed: $@";
453             # replace with existing object
454             if ( $@ =~ /The object name \S+ already exists/ ) {
455             # find existing object
456             my $existing_object = $self->find_icmpv4object({ name => $new_name });
457             say "\texisting object used";
458             return { %$existing_object{qw( id type )} };
459             }
460             elsif ( $@ =~ /conflicts with predefined name on device/ ) {
461             say "\t$@";
462             }
463             else {
464             croak "name update failed: $@";
465             }
466             }
467             }
468              
469              
470 0     0 1   sub cleanup_hosts($self) {
  0            
  0            
471 0           for my $object ($self->list_hosts({ expanded => 'true' })->{items}->@*) {
472             try {
473             #say $object->{name};
474             #say Dumper($object);
475             if ($object->{name} =~ /^(.*)_Mask32$/) {
476             my $new_name = $1;
477             say 'renaming host ', $object->{name}, ' ⮕ ', $new_name;
478             $self->update_host($object, { name => $new_name });
479             }
480             # clear description
481             if ($object->{description} eq 'Created during ASA Migration') {
482             $self->update_host($object, { description => '' });
483             }
484             }
485 0           catch {
486             warn $@;
487             }
488             }
489             }
490              
491              
492             sub create_cleaned_accesspolicy (
493 0           $self,
494 0           $source_accesspolicy_name,
495 0     0 1   $optional = {}) {
  0            
  0            
496             my $destination_accesspolicy_name = exists $optional->{target_access_policy_name}
497             ? $optional->{target_access_policy_name}
498 0 0         : $source_accesspolicy_name . '-cleaned';
499              
500             my @accesspolicies = $self->list_accesspolicies({ expanded => 'true'
501 0           })->{items}->@*;
502 0           for my $accesspolicy (@accesspolicies) {
503             next
504 0 0         unless $accesspolicy->{name} eq $source_accesspolicy_name;
505 0           say "cleaning " . $accesspolicy->{id}, ': ', $accesspolicy->{name};
506             #say Dumper($accesspolicy);
507             #say "creating new accesspolicy: " . Dumper($accesspolicy);
508              
509             # check if the cleaned accesspolicy already exists, in that case
510             # resume
511 0           my $new_accesspolicy;
512 0           for my $accesspolicy (@accesspolicies) {
513 0 0         if ($accesspolicy->{name} eq $destination_accesspolicy_name) {
514 0           $new_accesspolicy = $accesspolicy;
515 0           last;
516             }
517             }
518              
519 0           my $resume_rulenumber;
520 0 0         if (defined $new_accesspolicy) {
521             # find first rule to resume cleanup
522             my @rules = $self->list_accessrules($new_accesspolicy->{id})
523 0           ->{items}->@*;
524 0           $resume_rulenumber = scalar @rules + 1;
525 0           say "resuming cleanup of $destination_accesspolicy_name ",
526             "at rule #$resume_rulenumber\n";
527             }
528             else {
529 0           $new_accesspolicy = $self->create_accesspolicy({
530             name => $destination_accesspolicy_name,
531             defaultAction => {
532             action => 'BLOCK',
533             logBegin => 1,
534             sendEventsToFMC => 1,
535             },
536             });
537             }
538             #say Dumper($new_accesspolicy);
539              
540 0           my $rulenumber = 1;
541 0           RULE: for my $rule ($self->list_accessrules($accesspolicy->{id},
542             { expanded => 'true' })->{items}->@*) {
543 0 0 0       if ( defined $resume_rulenumber
544             && $rulenumber < $resume_rulenumber ) {
545 0           $rulenumber++;
546 0           next RULE;
547             }
548              
549             #next RULE
550             # unless $rule->{name} eq 'outside_access_in#15-1';
551 0           say $rule->{name};
552             # copy all attributes of the existing rule to the new one
553 0           my $updated_data = clone($rule);
554             # remove attributes that are not needed/allowed in the create
555             # call
556 0           delete $updated_data->{id};
557 0           delete $updated_data->{links};
558 0           delete $updated_data->{metadata};
559 0           delete $updated_data->{commentHistoryList};
560             #my $rule_for_diff = clone($updated_data);
561              
562 0 0         if (exists $optional->{rule_name_coderef}) {
563 0           $updated_data->{name} = $optional->{rule_name_coderef}->($rulenumber, $rule);
564             }
565              
566 0           $rulenumber++;
567              
568 0           for my $networktype (qw( sourceNetworks destinationNetworks )) {
569 0           my $src_networks = $rule->{$networktype};
570 0           for my $key (keys $src_networks->%*) {
571 0 0         if ($key eq 'objects') {
572 0           $updated_data->{$networktype}->{objects} = [];
573             #say "old: " . Dumper($src_networks->{objects});
574 0           for my $network ($src_networks->{objects}->@*) {
575             #say Dumper($network);
576 0           my $name = $network->{name};
577 0           my $type = $network->{type};
578 0 0         if ( $name =~ /^DM_INLINE_/ ) {
579             # eliminate autogenerated NetworkGroups
580 0 0         if ( $type eq 'NetworkGroup' ) {
581 0           my $networkgroup = $self->get_networkgroup($network->{id});
582             my $object_count =
583             (exists $networkgroup->{objects}
584             ? scalar $networkgroup->{objects}->@*
585             : 0)
586             + (exists $networkgroup->{literals}
587             ? scalar $networkgroup->{literals}->@*
588 0 0         : 0);
    0          
589 0 0         if ( $object_count > 50 ) {
590 0           warn "\tnumber of objects (",
591             $object_count, ") would exceed ",
592             "current FMC limit of 50, ",
593             "keeping current contents\n";
594             }
595             else {
596 0           say "\tmoving contents of group $name directly into rule";
597 0           for my $objecttype (qw( objects literals )) {
598 0           for my $networkobject ($networkgroup->{$objecttype}->@*) {
599             #say Dumper($networkobject);
600 0           push $updated_data->{$networktype}->{$objecttype}->@*, $networkobject;
601             }
602             }
603             }
604             }
605             else {
606 0           warn "object type $type not supported, keeping original object!";
607 0           push $updated_data->{$networktype}->{objects}->@*, $network;
608             }
609             }
610             # keep non-autogenerated objects
611             else {
612 0           push $updated_data->{$networktype}->{objects}->@*, $network;
613             }
614             }
615             }
616             # copy all other contents
617             else {
618 0           $updated_data->{$networktype}->{$key} = $src_networks->{$key};
619             }
620             }
621             }
622              
623 0           my $ports = $rule->{destinationPorts};
624 0 0         if (exists $ports->{objects} ) {
625             $updated_data->{destinationPorts} = {
626 0           objects => []
627             };
628             #say "old: " . Dumper($ports->{objects});
629 0           for my $portobj ($ports->{objects}->@*) {
630             #say Dumper($portobj);
631 0           my $name = $portobj->{name};
632 0           my $type = $portobj->{type};
633              
634 0 0         if ( $name =~ /^DM_INLINE_/ ) {
635             # eliminate autogenerated PortObjectGroups
636 0 0         if ( $type eq 'ProtocolPortObject' ) {
    0          
    0          
637             push
638             $updated_data->{destinationPorts}->{objects}->@*,
639             $self->cleanup_protocolport({
640 0           %$portobj{qw( id type )} });
641             }
642             elsif ( $type eq 'ICMPV4Object' ) {
643             push
644             $updated_data->{destinationPorts}->{objects}->@*,
645             $self->cleanup_icmpv4object({
646 0           %$portobj{qw( id type )} });
647             }
648             elsif ( $type eq 'PortObjectGroup' ) {
649 0           say "\tmoving contents of group $name directly into rule";
650             #say Dumper($rule);
651 0           my $portobjectgroup = $self->get_portobjectgroup($portobj->{id});
652 0           for my $portobject ($portobjectgroup->{objects}->@*) {
653             #say Dumper($portobject);
654             my $object = $portobject->{type} eq
655             'ProtocolPortObject'
656             ? $self->cleanup_protocolport(
657             {%$portobject{qw( id type )}}
658             )
659 0 0         : {%$portobject{qw( id type )}};
660             push $updated_data->{destinationPorts}->{objects}->@*,
661 0           $object;
662             }
663             }
664             else {
665 0           warn "unhandled object type $type, keeping unmodified\n";
666 0           push $updated_data->{destinationPorts}->{objects}->@*, $portobj;
667             }
668              
669             #my $protocolportobject = $self->get_protocolportobject($portobj->{id});
670             #say Dumper($protocolportobject);
671             }
672             # keep non-autogenerated objects
673             else {
674 0           push $updated_data->{destinationPorts}->{objects}->@*, $portobj;
675             }
676             }
677             }
678             #say "new: " . Dumper($updated_data);
679             # always replace existing destinationPorts because one of
680             # them might have been replaced with an existing one
681             # FIXME: check if literals get lost
682             #$self->update_accessrule($accesspolicy->{id}, $rule, $updated_data)
683             # if $updated_data->%*;
684             #say Dumper($updated_data);
685             #use Test::Differences;
686             #eq_or_diff($updated_data, $rule_for_diff, 'rule');
687 0           $self->create_accessrule($new_accesspolicy->{id}, $updated_data);
688             #last RULE;
689             }
690             }
691             }
692              
693             1;
694              
695             __END__
696              
697             =pod
698              
699             =encoding UTF-8
700              
701             =head1 NAME
702              
703             Net::Cisco::FMC::v1 - Cisco Firepower Management Center (FMC) API version 1 client library
704              
705             =head1 VERSION
706              
707             version 0.008000
708              
709             =head1 SYNOPSIS
710              
711             use strict;
712             use warnings;
713             use Net::Cisco::FMC::v1;
714             use Data::Dumper::Concise;
715              
716             my $fmc = Net::Cisco::FMC::v1->new(
717             server => 'https://fmcrestapisandbox.cisco.com',
718             user => 'admin',
719             passwd => '$password',
720             clientattrs => { timeout => 30 },
721             );
722              
723             # login to populate domains
724             $fmc->login;
725              
726             # list all domain uuids and names
727             print Dumper($fmc->domains);
728             # switch domain
729             $fmc->domain_uuid("e276abec-e0f2-11e3-8169-6d9ed49b625f");
730              
731             =head1 DESCRIPTION
732              
733             This module is a client library for the Cisco Firepower Management
734             Center (FMC) REST API version 1.
735             Currently it is developed and tested against FMC version 6.2.3.6.
736              
737             =head1 ATTRIBUTES
738              
739             =head2 domains
740              
741             Returns a list of hashrefs containing name and uuid of all domains which gets
742             populated by L</login>.
743              
744             =head2 domain_uuid
745              
746             The UUID of the domain which is used by all methods.
747              
748             =head1 METHODS
749              
750             =head2 login
751              
752             Logs into the FMC by fetching an authentication token via http basic
753             authentication.
754              
755             =head2 relogin
756              
757             Refreshes the session by loging in again (not using the refresh token) and
758             restores the currently set domain_uuid.
759              
760             =head2 logout
761              
762             Logs out of the FMC.
763              
764             =head2 create_accessrule
765              
766             Takes an access policy id, a hashref of the rule which should be created and
767             optional query parameters.
768              
769             =head2 list_accessrules
770              
771             Takes an access policy id and query parameters and returns a hashref with a
772             single key 'items' that has a list of access rules similar to the FMC API.
773              
774             =head2 get_accessrule
775              
776             Takes an access policy id, rule id and query parameters and returns the access
777             rule.
778              
779             =head2 update_accessrule
780              
781             Takes an access policy id, rule object and a hashref of the rule and returns
782             a hashref of the updated access rule.
783              
784             =head2 delete_accessrule
785              
786             Takes an access policy id and a rule object id.
787              
788             Returns true on success.
789              
790             =head2 list_deployabledevices
791              
792             Takes optional query parameters and returns a hashref with a
793             single key 'items' that has a list of deployable devices similar to the FMC
794             API.
795              
796             =head2 create_deploymentrequest
797              
798             Takes a hashref of deployment parameters.
799              
800             Returns the created task in the ->{metadata}->{task} hashref.
801              
802             =head2 get_task
803              
804             Takes a task id and returns its status.
805              
806             =head2 wait_for_task
807              
808             Takes a task id and an optional callback and checks its status every second
809             until it isn't in-progress any more.
810             The in-progress status is different for each task type, currently only
811             'DEVICE_DEPLOYMENT' is supported.
812             The callback coderef which is called for every check with the task as argument.
813              
814             Returns the task.
815              
816             =head2 cleanup_protocolport
817              
818             Takes a ProtocolPortObject and renames it to protocol_port, e.g. tcp_443.
819             If it has no port 'any' is used instead of the port number no avoid
820             'predefined name' errors.
821             Returns the ProtocolPortObject with the updated attributes.
822              
823             =head2 cleanup_icmpv4object
824              
825             Takes a ICMPv4Object and renames it to protocol_type[_code], e.g. icmp_8_0.
826             If it has no code only protocol and type is used.
827              
828             =head2 cleanup_hosts
829              
830             =over
831              
832             =item removes '_Mask32' from the name
833              
834             =item removes the description if it is 'Created during ASA Migration'
835              
836             =back
837              
838             =head2 create_cleaned_accesspolicy
839              
840             Takes an access policy name and a hashref of optional arguments.
841              
842             =head3 Optional arguments
843              
844             =over
845              
846             =item target_access_policy_name
847              
848             Defaults to access policy name with the postfix '-cleaned'.
849              
850             =item rule_name_coderef
851              
852             Gets passed the rule number and rule object and must return the new rule name.
853              
854             =back
855              
856             Creates a new access policy with the target name containing all rules of the
857             input access policy but cleaned by the following rules:
858              
859             =over
860              
861             =item the commentHistoryList is omitted
862              
863             =item replace autogenerated DM_INLINE_ NetworkGroups by their content
864              
865             Only if they don't contain more than 50 items because of the current limit in
866             FMC.
867              
868             =item replace autogenerated DM_INLINE_ PortObjectGroups by their content
869              
870             =item optional: the rule name is generated
871              
872             By passing a coderef named 'rule_name_coderef' in the optional arguments
873             hashref.
874              
875             =back
876              
877             The new access policy is created with a defaultAction of:
878              
879             action => 'BLOCK'
880             logBegin => true
881             sendEventsToFMC => true
882              
883             This is mainly for access policies migrated by the Cisco Firepower Migration
884             Tool from a Cisco ASA.
885              
886             Supports resuming.
887              
888             =head1 KNOWN BUGS
889              
890             Older FMC versions have bugs like:
891              
892             =over
893              
894             =item truncated JSON responses
895              
896             No workaround on client side possible, only a FMC update helps.
897              
898             =item no response to the 11th call (version 6.2.2.1)
899              
900             No workaround on client side because newer FMC versions (at least 6.2.3.6)
901             throttle the login call too.
902              
903             =item accessrule is created but error 'You do not have the required
904             authorization to do this operation' is thrown (version 6.2.2)
905              
906             No workaround on client side possible, only a FMC update helps.
907              
908             =back
909              
910             =head1 AUTHOR
911              
912             Alexander Hartmaier <abraxxa@cpan.org>
913              
914             =head1 COPYRIGHT AND LICENSE
915              
916             This software is copyright (c) 2018 - 2023 by Alexander Hartmaier.
917              
918             This is free software; you can redistribute it and/or modify it under
919             the same terms as the Perl 5 programming language system itself.
920              
921             =cut