Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api improvements #120

Merged
merged 3 commits into from Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 35 additions & 35 deletions README.md
Expand Up @@ -12,6 +12,7 @@
- [Optional Parameters](#optional-parameters)
- [Configuring AWS credentials](#configuring-aws-credentials)
- [Enabling SSL](#enabling-ssl)
- [Configuring OAuth2 in the endpoints](#configuring-oauth2-in-the-endpoints)
- [Change the default configuration](#change-the-default-configuration)
- [API usage](#api-usage)
- [Interacting with the processors' generated information](#interacting-with-the-processors-generated-information)
Expand Down Expand Up @@ -99,41 +100,6 @@ The frontend site uses HTTPs, in order to have it communicate with the processor
Make sure you have `Docker` installed on your device by following [these instructions](https://docs.docker.com/install/linux/docker-ce/debian).
The command that you need to execute will depend on the chosen device because each one has an independent Dockerfile.


##### Configuring OAuth2 in the endpoints

By default, all the endpoints exposed by the processors are accessible by everyone with access to the LAN. To avoid this vulnerability, the processor includes the possibility of configuring OAuth2 to keep your API secure.

To configure OAuth2 in the processor you need to follow these steps:
1. Enabling OAuth2 in the API by setting in `True` the parameter `UseAuthToken` (included in the `API` section).
2. Set into the container the env `SECRET_ACCESS_KEY`. This env is used to encode the JWT token. An easy way to do that is
to create a `.env` file (following the template `.env.example`) and pass the flag ```--env-file .env ``` when you run the processor.
3. Create an API user. You can do that in two ways:
1. Using the `create_api_user.py` script:

Inside the docker container, execute the script `python3 create_api_user.py --user=<USER> --password=<PASSWORD>`. For example, if you are using an x86 device, you can execute the following script.
```bash
docker run -it -p HOST_PORT:8000 -v "$PWD":/repo -e TZ=`./timezone.sh` neuralet/smart-social-distancing:latest-x86_64 python3 create_api_user.py --user=<USER> --password=<PASSWORD>
```
2. Using the `/auth/create_api_user` endpoint:
Send a POST request to the endpoint `http://<PROCESSOR_HOST>:<PROCESSOR_PORT>/auth/create_api_user` with the following body:
```
{
"user": <USER>,
"password": <PASSWORD>
}
```

After executing one of these steps, the `user` and `password` (hashed) will be stored in the file `/repo/data/auth/api_user.txt` inside the container. To avoid losing that file when the container is restarted, we recommend mounting the `/repo` directory as a volume.
4. Request a valid token. You can obtain one by sending a PUT request to the endpoint `http://<PROCESSOR_HOST>:<PROCESSOR_PORT>/auth/access_token` with the following body:
```
{
"user": <USER>,
"password": <PASSWORD>
}
```
The obtained token will be valid for 1 week (then a new one must be requested from the API) and needs to be sent as an `Authorization` header in all the requests. If you don't send the token (when the `UseAuthToken` attribute is set in `True`), you will receive a `401 Unauthorized` response.

##### Run on Jetson Nano
* You need to have JetPack 4.3 installed on your Jetson Nano.

Expand Down Expand Up @@ -280,6 +246,40 @@ If you don't have a certificate for the processor, you can create a self-signed

As you are using a self-signed certificate you will need to import the created CA (using the `.pem` file) in your browser as a trusted CA.

### Configuring OAuth2 in the endpoints

By default, all the endpoints exposed by the processors are accessible by everyone with access to the LAN. To avoid this vulnerability, the processor includes the possibility of configuring OAuth2 to keep your API secure.

To configure OAuth2 in the processor you need to follow these steps:
1. Enabling OAuth2 in the API by setting in `True` the parameter `UseAuthToken` (included in the `API` section).
2. Set into the container the env `SECRET_ACCESS_KEY`. This env is used to encode the JWT token. An easy way to do that is
to create a `.env` file (following the template `.env.example`) and pass the flag ```--env-file .env ``` when you run the processor.
3. Create an API user. You can do that in two ways:
1. Using the `create_api_user.py` script:

Inside the docker container, execute the script `python3 create_api_user.py --user=<USER> --password=<PASSWORD>`. For example, if you are using an x86 device, you can execute the following script.
```bash
docker run -it -p HOST_PORT:8000 -v "$PWD":/repo -e TZ=`./timezone.sh` neuralet/smart-social-distancing:latest-x86_64 python3 create_api_user.py --user=<USER> --password=<PASSWORD>
```
2. Using the `/auth/create_api_user` endpoint:
Send a POST request to the endpoint `http://<PROCESSOR_HOST>:<PROCESSOR_PORT>/auth/create_api_user` with the following body:
```
{
"user": <USER>,
"password": <PASSWORD>
}
```

After executing one of these steps, the `user` and `password` (hashed) will be stored in the file `/repo/data/auth/api_user.txt` inside the container. To avoid losing that file when the container is restarted, we recommend mounting the `/repo` directory as a volume.
4. Request a valid token. You can obtain one by sending a PUT request to the endpoint `http://<PROCESSOR_HOST>:<PROCESSOR_PORT>/auth/access_token` with the following body:
```
{
"user": <USER>,
"password": <PASSWORD>
}
```
The obtained token will be valid for 1 week (then a new one must be requested from the API) and needs to be sent as an `Authorization` header in all the requests. If you don't send the token (when the `UseAuthToken` attribute is set in `True`), you will receive a `401 Unauthorized` response.

### Change the default configuration
You can read and modify the configurations in `config-*.ini` files, accordingly:

Expand Down
2 changes: 1 addition & 1 deletion api/models/core.py
Expand Up @@ -5,5 +5,5 @@

class CoreDTO(SnakeModel):
host: str = Field("0.0.0.0", example="0.0.0.0")
queuePort: str = Field("8010", example="8010")
queuePort: int = Field(8010, example=8010)
queueAuthKey: str = Field("", example="shibalba")
1 change: 1 addition & 0 deletions api/processor_api.py
Expand Up @@ -37,6 +37,7 @@
logger = logging.getLogger(__name__)
logging.getLogger().setLevel(logging.INFO)


class ProcessorAPI:
"""
The ProcessorAPI object implements a fastapi application that should allow configuring, starting and stopping processing,
Expand Down
4 changes: 3 additions & 1 deletion api/routers/app.py
Expand Up @@ -25,4 +25,6 @@ def update_app_config(app: AppDTO, reboot_processor: Optional[bool] = True):
app_dict = map_to_config_file_format(app)
config_dict["App"] = app_dict
success = update_config(config_dict, reboot_processor)
return handle_response(app_dict, success)
if not success:
return handle_response(app_dict, success)
return map_section_from_config("App", extract_config())
8 changes: 6 additions & 2 deletions api/routers/area_loggers.py
Expand Up @@ -68,7 +68,9 @@ async def create_logger(new_logger: AreaLoggerDTO, reboot_processor: Optional[bo
index = max(loggers_index) + 1
config_dict[f"AreaLogger_{index}"] = logger_file
success = update_config(config_dict, reboot_processor)
return handle_response(logger_file, success, status.HTTP_201_CREATED)
if not success:
return handle_response(logger_file, success, status.HTTP_201_CREATED)
return next((ps for ps in get_area_loggers() if ps["name"] == logger_file["Name"]), None)


@area_loggers_router.put("/{logger_name}", response_model=AreaLoggerDTO)
Expand Down Expand Up @@ -97,7 +99,9 @@ async def edit_logger(logger_name: str, edited_logger: AreaLoggerDTO,
logger_file = map_to_config_file_format(edited_logger, True)
config_dict[edited_logger_section] = logger_file
success = update_config(config_dict, reboot_processor)
return handle_response(logger_file, success)
if not success:
return handle_response(logger_file, success)
return next((ps for ps in get_area_loggers() if ps["name"] == logger_name), None)


@area_loggers_router.delete("/{logger_name}", status_code=status.HTTP_204_NO_CONTENT)
Expand Down
10 changes: 6 additions & 4 deletions api/routers/areas.py
Expand Up @@ -60,9 +60,10 @@ async def create_area(new_area: AreaConfigDTO, reboot_processor: Optional[bool]
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"The cameras: {non_existent_cameras} do not exist")
area_dict = map_to_config_file_format(new_area)
config_dict[f"Area_{len(areas)}"] = area_dict

success = update_config(config_dict, reboot_processor)
return handle_response(area_dict, success, status.HTTP_201_CREATED)
if not success:
return handle_response(area_dict, success, status.HTTP_201_CREATED)
return next((area for area in get_areas() if area["id"] == area_dict["Id"]), None)


@areas_router.put("/{area_id}", response_model=AreaConfigDTO)
Expand All @@ -89,9 +90,10 @@ async def edit_area(area_id: str, edited_area: AreaConfigDTO, reboot_processor:

area_dict = map_to_config_file_format(edited_area)
config_dict[f"Area_{index}"] = area_dict

success = update_config(config_dict, reboot_processor)
return handle_response(area_dict, success)
if not success:
return handle_response(area_dict, success)
return next((area for area in get_areas() if area["id"] == area_id), None)


@areas_router.delete("/{area_id}", status_code=status.HTTP_204_NO_CONTENT)
Expand Down
8 changes: 6 additions & 2 deletions api/routers/cameras.py
Expand Up @@ -159,7 +159,9 @@ async def create_camera(new_camera: CameraDTO, reboot_processor: Optional[bool]
camera_dict = map_to_camera_file_format(new_camera)
config_dict[f"Source_{len(cameras)}"] = camera_dict
success = update_config(config_dict, reboot_processor)
return handle_response(camera_dict, success, status.HTTP_201_CREATED)
if not success:
return handle_response(camera_dict, success, status.HTTP_201_CREATED)
return next((camera for camera in get_cameras(["withImage"]) if camera["id"] == camera_dict["Id"]), None)


@cameras_router.put("/{camera_id}", response_model=CameraDTO)
Expand All @@ -173,7 +175,9 @@ async def edit_camera(camera_id: str, edited_camera: CameraDTO, reboot_processor
camera_dict = map_to_camera_file_format(edited_camera)
config_dict[f"Source_{index}"] = map_to_camera_file_format(edited_camera)
success = update_config(config_dict, reboot_processor)
return handle_response(camera_dict, success)
if not success:
return handle_response(camera_dict, success)
return next((camera for camera in get_cameras(["withImage"]) if camera["id"] == camera_id), None)


@cameras_router.delete("/{camera_id}", status_code=status.HTTP_204_NO_CONTENT)
Expand Down
4 changes: 3 additions & 1 deletion api/routers/classifier.py
Expand Up @@ -26,4 +26,6 @@ def update_classifier_config(classifier: ClassifierDTO, reboot_processor: Option
classifier_dict = map_to_config_file_format(classifier)
config_dict["Classifier"] = classifier_dict
success = update_config(config_dict, reboot_processor)
return handle_response(classifier_dict, success)
if not success:
return handle_response(classifier_dict, success)
return map_section_from_config("Classifier", extract_config())
4 changes: 3 additions & 1 deletion api/routers/config.py
Expand Up @@ -91,7 +91,9 @@ async def update_config_file(config: ConfigDTO, reboot_processor: Optional[bool]
"""
config_dict = map_to_file_format(config)
success = update_config(config_dict, reboot_processor)
return handle_response(config_dict, success)
if not success:
return handle_response(config_dict, success)
return map_config(extract_config(), "")


@config_router.get("/info", response_model=ConfigInfo)
Expand Down
4 changes: 3 additions & 1 deletion api/routers/core.py
Expand Up @@ -25,4 +25,6 @@ def update_core_config(core: CoreDTO, reboot_processor: Optional[bool] = True):
core_dict = map_to_config_file_format(core)
config_dict["CORE"] = core_dict
success = update_config(config_dict, reboot_processor)
return handle_response(core_dict, success)
if not success:
return handle_response(core_dict, success)
return map_section_from_config("CORE", extract_config())
4 changes: 3 additions & 1 deletion api/routers/detector.py
Expand Up @@ -25,4 +25,6 @@ def update_detector_config(detector: DetectorDTO, reboot_processor: Optional[boo
detector_dict = map_to_config_file_format(detector)
config_dict["Detector"] = detector_dict
success = update_config(config_dict, reboot_processor)
return handle_response(detector_dict, success)
if not success:
return handle_response(detector_dict, success)
return map_section_from_config("Detector", extract_config())