|
| 1 | +How to Contribute |
| 2 | +================= |
| 3 | +Thank you for your interest in contributing to this project! Community support is essential to keep this package secure, |
| 4 | +efficient, and useful. Your contributions are very much appreciated. To ensure this project can achieve and retain a |
| 5 | +production ready package, please follow the guidelines detailed in this document. |
| 6 | + |
| 7 | +Requirements |
| 8 | +------------ |
| 9 | +If you plan on contributing, please follow these basic requirements |
| 10 | +- Do not introduce known vulnerabilities. Please do your due diligence before integrating anything that may be insecure. |
| 11 | +- Be respectful. Open source contributions are a learning experience for all developers regardless of skill level. |
| 12 | + Please be kind and helpful towards other contributors. |
| 13 | +- Write ethical code. Please do not steal code and claim it as your own. |
| 14 | + |
| 15 | +Testing |
| 16 | +------- |
| 17 | +It is preferred that any API endpoints or core features of this package include unit tests at the time they are written. |
| 18 | +This will help speed up pull requests, and assist in future regression testing. For example, if you write a new API |
| 19 | +endpoint please include a unit test that will thoroughly test the extent of that endpoint. Python3 is the preferred |
| 20 | +language for unit tests. Seeing as changes will primarily be to API endpoints, please consider Python3's `requests` |
| 21 | +module. |
| 22 | + |
| 23 | +Proposing Changes |
| 24 | +----------------- |
| 25 | +A pull request is required for any proposed change. It is preferred that you fork the main project, make your changes, |
| 26 | +then submit a pull request to merge your changes to the main project's current development branch. Once merged, your |
| 27 | +changes will be made available upon the next release. |
| 28 | + |
| 29 | +Coding Conventions |
| 30 | +------------------ |
| 31 | +Make an attempt to match the format of existing code. The basic conventions are: |
| 32 | +- Comments are recommended as long as they are brief, meaningful, and short |
| 33 | +- Variables should be defined using snake case (e.g. snake_case) |
| 34 | +- Functions should be defined using snake case (e.g. snake_case()) |
| 35 | +- Constants should be defined using upper snake case (e.g. SNAKE_CASE) |
| 36 | +- Classes should defined using pascal case (e.g. PascalCase) |
| 37 | +- Lines should not contain more than 128 characters<br> |
| 38 | +_Note: suggestions to coding conventions are welcome, refactoring as things change is necessary to support maintainable |
| 39 | +code_ |
| 40 | + |
| 41 | +Writing new API Endpoints |
| 42 | +--------------------- |
| 43 | +Most contributions to this project will be in the form of integrating new API endpoints. API endpoints are comprised of |
| 44 | +a few different components. It is strongly recommended that you familiarize yourself with pfSense's PHP shell before |
| 45 | +diving into creating endpoints. To get started writing your own endpoints, please follow the steps below: |
| 46 | + |
| 47 | +### Things to Know ### |
| 48 | + - The API is based on REST principals. Unfortunately, pfSense does not allow any custom changes to the NGINX |
| 49 | + configuration so alternate request methods like `PUT` and `DELETE` do not appear to be possible. To accommodate this, |
| 50 | + the requested action must be defined in the endpoint path. |
| 51 | + - Create actions must be a `POST` request to an endpoint ending in `/add/` |
| 52 | + (e.g. `https://localhost/api/v1/firewall/rules/add/`) |
| 53 | + - Read actions must be a `GET` request to a base endpoint (e.g. `https://localhost/api/v1/firewall/rules/`) |
| 54 | + - Update actions must be a `POST` request to an endpoint ending in `/update/` |
| 55 | + (e.g. `https://localhost/api/v1/firewall/rules/update/`) |
| 56 | + - Delete actions must be a `POST` request to an endpoint ending in `/delete/` |
| 57 | + (e.g. `https://localhost/api/v1/firewall/rules/delete/`) |
| 58 | + |
| 59 | +### Writing the API caller ### |
| 60 | +At the core of the API endpoint is the API caller. This is a function that validates client request data, writes changes |
| 61 | +to the pfSense configuration file, makes any corresponding system changes, and returns the requested data as an array. |
| 62 | + |
| 63 | +1. Most API endpoints are designed to allow programmatic changes to configuration that is usually made in the pfSense |
| 64 | +webConfigurator. To get a basic understanding on what your API call will need to do, look at the PHP code of the |
| 65 | +corresponding webConfigurator page (found within `/usr/local/www/` of your pfSense installation). You should be able to |
| 66 | +get a good idea of what input validation is needed, and what core pfSense functions you will need to call to achieve |
| 67 | +these changes. You can also use existing API call functions (found in `/files/etc/inc/apicalls.inc` within this repo) as |
| 68 | +a reference! |
| 69 | + |
| 70 | +2. Write your function in `/files/etc/inc/apicalls.inc`. The function name should match the URL filepath without the |
| 71 | +version. For example, for an API endpoint at URL `/api/v1/firewall/rules/delete/`, the function would be named |
| 72 | +`api_firewall_rules_delete`. Please also place this function next to any related functions in `apicall.inc`. For example |
| 73 | +if you write a new API call function for `api_firewall_rules_update`, place it next to any existing functions for the |
| 74 | +API firewall calls. |
| 75 | + |
| 76 | +3. Ensure API callers always return the data of the action that was performed. For example, if you are writing an |
| 77 | +`update` endpoint, ensure the API callers returns the updated data. Or if you are writing a `delete` endpoint, ensure |
| 78 | +the API caller always returns the deleted data. |
| 79 | + |
| 80 | +4. Ensure any validation or API errors encountered when the API caller function is run returns a corresponding error |
| 81 | +from the `/files/etc/inc/apiresp.inc` file. You can read more about writing API error responses below. |
| 82 | + |
| 83 | +Here is an example structure of an API caller function: |
| 84 | +``` |
| 85 | +function api_caller_function() { |
| 86 | + # VARIABLES |
| 87 | + global $err_lib, $config, $api_resp, $client_params; |
| 88 | + $read_only_action = true; // Set whether this action requires read only access |
| 89 | + $req_privs = array("page-all", "page-some-other-webconfigurator-permission"); // Array of privs allowed |
| 90 | + $http_method = $_SERVER['REQUEST_METHOD']; // Get our HTTP method |
| 91 | +
|
| 92 | + # RUN TIME |
| 93 | + // Check that client is authenticated and authorized |
| 94 | + if (api_authorized($req_privs, $read_only_action)) { |
| 95 | + // Check that our HTTP method is GET (READ) |
| 96 | + if ($http_method === 'GET') { |
| 97 | + // ADD YOUR INPUT VALIDATION, CONFIG WRITES, AND SYSTEM CONFIGURATION |
| 98 | + // Return our response |
| 99 | + $api_resp = array("status" => "ok", "code" => 200, "return" => 0, "message" => "", "data" => []); |
| 100 | + return $api_resp; |
| 101 | + } else { |
| 102 | + $api_resp = array("status" => "bad request", "code" => 400, "return" => 2); |
| 103 | + $api_resp["message"] = $err_lib[$api_resp["return"]]; |
| 104 | + return $api_resp; |
| 105 | + } |
| 106 | + } else { |
| 107 | + return $api_resp; // Returns default unauthorized response |
| 108 | + } |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +### Writing API responses ### |
| 113 | +The API uses a centralized API response array (found in `/file/etc/inc/apiresp.inc` of this repo). Each response |
| 114 | +corresponds with a unique code that can be used to get the response message, status, etc. This is particularly helpful |
| 115 | +when API response messages need to be changed as it is always in one central location. |
| 116 | + |
| 117 | +To create a new API response: |
| 118 | + |
| 119 | +1. Pick a numeric code that is not already in use in the `$err_lib` array within the `api_error_lib()` function of |
| 120 | +`/files/etc/inc/apiresp.inc`. Try to use a code that is within the reserved range of the API endpoint you are creating. |
| 121 | +2. Add a new array item to the `$err_lib` array within the `api_error_lib()`. The associated ID should be the numeric |
| 122 | +code you picked, and the value should be the descriptive message to return from the API. Some examples are: |
| 123 | +``` |
| 124 | +$err_lib = array( |
| 125 | + // 0-999 reserved for system API level responses |
| 126 | + 0 => "Success", |
| 127 | + 1 => "Process encountered unexpected error", |
| 128 | + 2 => "Invalid HTTP method", |
| 129 | + 3 => "Authentication failed" |
| 130 | +) |
| 131 | +``` |
| 132 | +3. To get the response message, ensure your API caller function has `$err_lib` declared globally |
| 133 | +(e.g. `global $err_lib;`), then you can pull the message corresponding with your code as such `$err_lib[<CODE>]`. Each |
| 134 | +API caller should return a response error similar to: |
| 135 | +``` |
| 136 | +array( |
| 137 | + "status" => "unauthorized", # Sets the descriptive HTTP response message (unauthorized, not found, ok, etc.) |
| 138 | + "code" => 401, # Sets the HTTP response code. This will be the response code PHP returns to the client. |
| 139 | + "return" => 3, # Set the unique API response code from `apiresp.inc` |
| 140 | + "message" => $err_lib[3], # Pull the message corresponding with this unique response code from `apiresp.inc` |
| 141 | + "data" => [] # Set the data to return to the client in an array format |
| 142 | +);` |
| 143 | +``` |
| 144 | + |
| 145 | +### Writing API endpoint listeners ### |
| 146 | +Each API caller must have an API endpoint listener within the web path to listen for requests and execute the API caller |
| 147 | +function. These can be found in `/files/usr/local/www/api/v1/`. To create a new endpoint: |
| 148 | + |
| 149 | +1. Create a new directory in `/files/usr/local/www/api/v1/` that corresponds with the endpoint you are creating. For |
| 150 | +example, if you are creating a new endpoint that deletes a firewall rule, you would create the directory |
| 151 | +`/files/usr/local/www/api/v1/firewall/rules/delete/`. |
| 152 | +2. Create an index.php file within that directory and add the following code: |
| 153 | +``` |
| 154 | +<?php |
| 155 | +# IMPORTS |
| 156 | +require_once("apicalls.inc"); |
| 157 | +
|
| 158 | +# RUN API CALL |
| 159 | +$resp = your_api_caller_function(); # <--- BE SURE TO CHANGE THIS TO YOUR API CALL FUNCTION |
| 160 | +http_response_code($resp["code"]); |
| 161 | +echo json_encode($resp) . PHP_EOL; |
| 162 | +exit(); |
| 163 | +``` |
| 164 | +This expects the API caller function to return a associative array. This will then set the HTTP response code based on |
| 165 | +the "code" value of your functions returned array. The returned array with then be serialized to JSON and returned to |
| 166 | +the client. |
| 167 | + |
| 168 | +### Writing tool functions ### |
| 169 | +Often times you will need to create functions to condense redundant tasks. You can place any necessary tool functions in |
| 170 | +`/files/etc/inc/api.inc`. |
| 171 | + |
| 172 | +### Adding endpoint to the package |
| 173 | +After you have written your API endpoint and have tested it's functionality, you must specify your endpoint files in |
| 174 | +the package makefile. Otherwise, it will not be included in the package in the next release. |
| 175 | + |
| 176 | +1. Add the following lines to the `Makefile` located in this repo. **Be sure to change the file paths to match the files |
| 177 | +you have created**: |
| 178 | +``` |
| 179 | +${MKDIR} ${STAGEDIR}${PREFIX}/www/api/v1/status/carp/modify |
| 180 | +${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/api/v1/status/carp/modify/index.php \ |
| 181 | + ${STAGEDIR}${PREFIX}/www/api/v1/status/carp/modify |
| 182 | +``` |
| 183 | +2. Add the following lines to the `pkg-plist` file located in this repo. Be sure to change the file paths to match the |
| 184 | +files you have created: |
| 185 | + - For each directory created, add: `@dir /usr/local/www/api/v1/status/carp/modify` |
| 186 | + - For each index.php file created, add `/usr/local/www/api/v1/status/carp/modify/index.php` |
| 187 | + |
| 188 | +Questions |
| 189 | +--------- |
| 190 | +There are some complex components to this project. Please feel free to reach out with any questions, |
| 191 | +issues, or insight you have when creating API endpoints. Time permitting I am happy to help any way I can. |
0 commit comments