1# This code is part of Ansible, but is an independent component. 2# This particular file snippet, and this file snippet only, is BSD licensed. 3# Modules you write using this snippet, which is embedded dynamically by Ansible 4# still belong to the author of the module, and may assign their own license 5# to the complete work. 6# 7# (c) 2019 Fortinet, Inc 8# All rights reserved. 9# 10# Redistribution and use in source and binary forms, with or without modification, 11# are permitted provided that the following conditions are met: 12# 13# * Redistributions of source code must retain the above copyright 14# notice, this list of conditions and the following disclaimer. 15# * Redistributions in binary form must reproduce the above copyright notice, 16# this list of conditions and the following disclaimer in the documentation 17# and/or other materials provided with the distribution. 18# 19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29 30from __future__ import (absolute_import, division, print_function) 31__metaclass__ = type 32 33DOCUMENTATION = """ 34--- 35author: 36 - Miguel Angel Munoz (@magonzalez) 37httpapi : fortios 38short_description: HttpApi Plugin for Fortinet FortiOS Appliance or VM 39description: 40 - This HttpApi plugin provides methods to connect to Fortinet FortiOS Appliance or VM via REST API 41version_added: "2.9" 42""" 43 44from ansible.plugins.httpapi import HttpApiBase 45from ansible.module_utils.basic import to_text 46from ansible.module_utils.six.moves import urllib 47import json 48import re 49 50 51class HttpApi(HttpApiBase): 52 def __init__(self, connection): 53 super(HttpApi, self).__init__(connection) 54 55 self._ccsrftoken = '' 56 57 def set_become(self, become_context): 58 """ 59 Elevation is not required on Fortinet devices - Skipped 60 :param become_context: Unused input. 61 :return: None 62 """ 63 return None 64 65 def login(self, username, password): 66 """Call a defined login endpoint to receive an authentication token.""" 67 68 data = "username=" + urllib.parse.quote(username) + "&secretkey=" + urllib.parse.quote(password) + "&ajax=1" 69 dummy, result_data = self.send_request(url='/logincheck', data=data, method='POST') 70 if result_data[0] != '1': 71 raise Exception('Wrong credentials. Please check') 72 73 def logout(self): 74 """ Call to implement session logout.""" 75 76 self.send_request(url='/logout', method="POST") 77 78 def update_auth(self, response, response_text): 79 """ 80 Get cookies and obtain value for csrftoken that will be used on next requests 81 :param response: Response given by the server. 82 :param response_text Unused_input. 83 :return: Dictionary containing headers 84 """ 85 86 headers = {} 87 resp_raw_headers = [] 88 if hasattr(response.headers, '_headers'): 89 resp_raw_headers = response.headers._headers 90 else: 91 resp_raw_headers = [(attr, response.headers[attr]) for attr in response.headers] 92 for attr, val in resp_raw_headers: 93 if attr.lower() == 'set-cookie' and 'APSCOOKIE_' in val: 94 headers['Cookie'] = val 95 # XXX: In urllib2 all the 'set-cookie' headers are coalesced into one 96 x_ccsrftoken_position = val.find('ccsrftoken=') 97 if x_ccsrftoken_position != -1: 98 token_string = val[x_ccsrftoken_position + len('ccsrftoken='):].split('\"')[1] 99 self._ccsrftoken = token_string 100 101 elif attr.lower() == 'set-cookie' and 'ccsrftoken=' in val: 102 csrftoken_search = re.search('\"(.*)\"', val) 103 if csrftoken_search: 104 self._ccsrftoken = csrftoken_search.group(1) 105 106 headers['x-csrftoken'] = self._ccsrftoken 107 108 return headers 109 110 def handle_httperror(self, exc): 111 """ 112 Not required on Fortinet devices - Skipped 113 :param exc: Unused input. 114 :return: exc 115 """ 116 return exc 117 118 def send_request(self, **message_kwargs): 119 """ 120 Responsible for actual sending of data to the connection httpapi base plugin. 121 :param message_kwargs: A formatted dictionary containing request info: url, data, method 122 123 :return: Status code and response data. 124 """ 125 url = message_kwargs.get('url', '/') 126 data = message_kwargs.get('data', '') 127 method = message_kwargs.get('method', 'GET') 128 129 try: 130 response, response_data = self.connection.send(url, data, method=method) 131 response_status = None 132 if hasattr(response, 'status'): 133 response_status = response.status 134 else: 135 response_status = response.headers.status 136 return response_status, to_text(response_data.getvalue()) 137 except Exception as err: 138 raise Exception(err) 139