Skip to content

Commit 173e306

Browse files
Added license, contribution instructions
1 parent 4db9aa4 commit 173e306

4 files changed

Lines changed: 392 additions & 2 deletions

File tree

CNAME

Lines changed: 0 additions & 1 deletion
This file was deleted.

CONTRIBUTING.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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

Comments
 (0)