Skip to content

Commit

Permalink
Merge pull request #11 from Local-Connectivity-Lab/ticket-484
Browse files Browse the repository at this point in the history
Ticket 484 - blocked users
  • Loading branch information
philion authored Feb 27, 2024
2 parents 7ff161b + 1883a68 commit 499eb82
Show file tree
Hide file tree
Showing 16 changed files with 856 additions and 172 deletions.
152 changes: 23 additions & 129 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,155 +1,49 @@
# netbot

[![Python application](https://github.com/philion/netbot/actions/workflows/python-app.yml/badge.svg?branch=main)](https://github.com/philion/netbot/actions/workflows/python-app.yml)

Testing currently requires VPN access, so automanted tests are skipped for now. To test manually, run `python -m unittest` from the commandline with a valid `.env` file.

community **NET**work discord **BOT**, for integrating network management functions

## Deploy netbot
To mangage authorization, security tokens and other credentials are stored in a local `.env` file and read by the code. The following credentials are needed for full integrarion:
```
DISCORD_TOKEN=ABC
REDMINE_TOKEN=123
REDMINE_URL=http://do/re/mi
IMAP_HOST=imap.example.com
[email protected]
IMAP_PASSWORD=u&me
```

`netbot` uses standard Docker compose:

To run `netbot` using a standard container system:
```
git clone https://github.com/philion/netbot
cd netbox
cp ~/.env ./.env
sudo docker compose up -d
```

to stop:
```
sudo docker compose down
```


## Discord Token & Invite Bot
To enable netbot on your Discord server, you need to generate a valid `DISCORD_TOKEN` and then invite the bot to your Discord instance.

1. Go to https://discord.com/developers/applications and login.
2. Create a **New Application** (top-right button).
2. Fill in the *Name* and click the Terms of Service.
3. Add a Description on the General Information page and save changes.
4. In the OAuth2/URL Generator section, generate a URL for a **bot** with **Administrator** permissions.
5. That URL will look something like:
https://discord.com/api/oauth2/authorize?client_id=[client-id]&permissions=8&scope=bot
6. Open a browser with that URL and login.
7. You'll be presented with a window to add your bot to any server you have admin permissions for.
8. Select your server and click OK.
4. Back in the bot setup, in the "Bot" section, under "Build-A-Bot", **Copy** the Token. This is the DISCORD_TOKEN
3. On the same page, a little lower, *unset* the **Public Bot** switch.
4. Under "Privlidged Gateway Intents", enable "Message Content Intent".
5. Create an `.env` file in the `netbot` directory containing:
`DISCORD_TOKEN=your-token-contents`

Note: Not all admin permissions are needed. If you want a very targeted bot, select:
* Manage Server
* Manage Roles
* Manage Channels
* Manage Expressions
* Read Messages/View Channels
* Send Messages
* Create Pubilc Threads
* Create Private Threads
* Send Messages in Threads
* Manage Messages
* Manage Threads
* Embed Links
* Attach Files
* Read Message History
* Use Slash Commands

## Deploy threader
The threader functionality, managed by `threader.py`, needs to be registered as a cron job to run every 5 minutes.

```
sudo crontab -e
```

and add the following entry to the bottom:
```
*/5 * * * * /home/scn/github/netbot/threader_job.sh | /usr/bin/logger -t threader
```

This cron job uses the Python venv to execute, and captures std and err output to syslog (tagged "threader").

Logs from the runs are stored in `/home/scn/github/netbot/logs` folder, one per cron jon with timestamps.
## community **NET**work discord **BOT**, for integrating network management functions

[![Python application](https://github.com/philion/netbot/actions/workflows/python-app.yml/badge.svg?branch=main)](https://github.com/philion/netbot/actions/workflows/python-app.yml)

## Discord Usage
The following Discord commands are implemented:

```
/scn add [login] - Map current discord userid to redmine [login]
/scn join [team] - discord user joins [team] (and maps user id)
/scn leave [team] - discord user leaves [team]
/scn reindex - rebuild the bot's index of users, teams and other metadata.
Use after adding new users and teams to Redmine.
A collection of Seattle Community Network ([SCN](https://seattlecommunitynetwork.org/)) tools, including:
* **`threader.ph`**: SCN's email threading service to collect and categorize SCN messages.
* **`netbot.py`**: SCN's Discord bot to manage Redmine tickets from Discord.
* **`cli.py`**: A rich-terminal CLI version of the `netbot` commands.
* **`redmine.py`**: A Redmine client written in Python, designed for SCN use cases.

/tickets me - (default) tickets assigned to me or my teams
/tickets [team] - tickets assigned to team [team]
/tickets [user] - tickets assigned to a specific [user]
/tickets [query] - tickets that match the term [query]
This code base currently supports several different services due to the reliance on the Redmine client code. (In the future, this should be split into several projects, and redmine.py cleaned up and submitted to PyPI.)

/ticket # show - (default) show ticket info for ticket #
/ticket # details - show ticket # with all notes (in a decent format)
/ticket # assign - Assign ticket n to the specified user - not yet implemented
/ticket # unassign - Mark ticket n new and unassigned.
/ticket # progress - Assign the ticket to yourself and set it yourself.
/ticket # resolve - Mark the ticket resolved.

/new [title] - Create a new ticket with the title [title]
```
## threader

## CLI
The email threader functionality is implemented in `threader.py`, is designed to run periodically as a cron job.

A command-line interface version of the `tickets` Discord bot command provides the same capablities as the bot on Discord. This CLI was developed to help testing, which the asynchonous nature of Discord interactions adds a layer of complexity to.
For design and implementation details, see [Design](docs/design.md).

```
cli.py - List the new tickets assigned to me or teams I'm on
cli.py [user] - List the tickets assigned to user
cli.py [#] - List details for ticket #
cli.py [query] - Search for tickets containing the term [query]
cli.py [#] assign [user] - Assign ticket # to the specified user
cli.py [#] unassign - Mark ticket # new and unassigned.
cli.py [#] progress - Assign ticket # to yourself and set it yourself.
cli.py [#] resolve - Mark ticket number # resolved.
```
For deployment and operational details, see [Threader Operation](docs/threader.md).

### CLI Configuration

To configure the API key needed for `cli.py` to access the Redmine server, create a API key as per: https://www.redmine.org/projects/redmine/wiki/Rest_api#Authentication, notably:
## netbot

> You can find your API key on your account page ( /my/account ) when logged in, on the right-hand pane of the default layout.
The netbot functionality is implemented in `netbot.py` (and supporting `cog_*.py` implementations), is designed to run in as a container using a standard `compose.yaml` file.

Once you have you API key, create a file in the local directoy named `.env`.
For design and implementation details, see [Design](docs/design.md).

In the new file, create entries for:
For deployment and operational details, see [Netbot Operation](docs/netbot.md).

REDMINE_TOKEN=[your redmine api token]
REDMINE_URL=http://your.redmine.server

Now you should be able to see a list of interesting tickets, specifically for the user with the supplied API key.
## cli

./cli.py
`cli.py` provides an implementation of the netbot Discord commands that can be run on a local command line.

For details about configuration and usage, see [CLI](docs/cli.md)

## Build and Test

## Development
A `Makefile` is provided with the following targets:
- `venv` : build a Python virtual environment ("venv") in `.venv`
- `venv` : build a Python virtual environment ("venv")
- `test` : run the unit test suite
- `coverage` : run the unit tests and generate a minimal coverage report
- `htmlcov` : run the unit tests and generate a full report in htmlcov/

Testing and coverage requires standing up a local testbed. For details, see [Design](docs/design.md).
31 changes: 31 additions & 0 deletions cog_scn.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,37 @@ async def teams(self, ctx:discord.ApplicationContext, teamname:str=None):
await ctx.respond(buff[:2000]) # truncate!


# ticket 484 - http://10.10.0.218/issues/484
# block users based on name (not discord membership)
@scn.command(description="block specific a email address and reject all related tickets")
async def block(self, ctx:discord.ApplicationContext, username:str):
log.debug(f"blocking {username}")
#user = self.redmine.lookup_user(username)
user = self.redmine.find_user(username)
if user:
# add the user to the blocked list
self.redmine.block_user(user)
# search and reject all tickets from that user
for ticket in self.redmine.get_tickets_by(user):
self.redmine.reject_ticket(ticket.id)
await ctx.respond(f"Blocked user: {user.login} and rejected all created tickets")
else:
log.debug("trying to block unknown user '{username}', ignoring")
await ctx.respond(f"Unknown user: {username}")


@scn.command(description="unblock specific a email address")
async def unblock(self, ctx:discord.ApplicationContext, username:str):
log.debug(f"unblocking {username}")
user = self.redmine.find_user(username)
if user:
self.redmine.unblock_user(user)
await ctx.respond(f"Unblocked user: {user.login}")
else:
log.debug("trying to unblock unknown user '{username}', ignoring")
await ctx.respond(f"Unknown user: {username}")


async def print_team(self, ctx, team):
msg = f"> **{team.name}**\n"
for user_rec in team.users:
Expand Down
44 changes: 44 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Netbot Command Line Interface (CLI)

`cli.py` provides command-line interface version of the `netbot` capablities.


## Configuration: `.env` file

`cli.py` relies on the following environment settings:
* `REDMINE_TOKEN`: Secure Redmine API token
* `REDMINE_URL`: URL of Redmine service

To configure the API key needed for `cli.py` to access the Redmine server, create a API key as per: https://www.redmine.org/projects/redmine/wiki/Rest_api#Authentication, notably:

> You can find your API key on your account page (/my/account) when logged in, on the right-hand pane of the default layout.
Once you have you API key, create a file in the local directoy named `.env`.

In the new file, create entries for:

```
REDMINE_TOKEN=[your redmine api token]
REDMINE_URL=http://your.redmine.server
```

Once the `.env` file has been created, the `cli.py` should "just work".


## CLI Usage

```
cli.py - List the new tickets assigned to me or teams I'm on
cli.py [user] - List the tickets assigned to user
cli.py [#] - List details for ticket #
cli.py [query] - Search for tickets containing the term [query]
cli.py [#] assign [user] - Assign ticket # to the specified user
cli.py [#] unassign - Mark ticket # new and unassigned.
cli.py [#] progress - Assign ticket # to yourself and set it yourself.
cli.py [#] resolve - Mark ticket number # resolved.
```


## Logging

All CLI logs are output to the console, and can be redirected using standard Unix tools.
99 changes: 99 additions & 0 deletions docs/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Netbot Design and Implementation

## netbot


### Discord User Mapping
To maintain accurate attribution in Redmine when comments are made in Discord, a mapping is setup between Discord users and Redmine users.

This mapping is stored in a custom field, "Discord ID", in the Redmine user data.


## Email Threader

cron job

script to encapsulate loadin python environment

processes each new IMAP message:
- extracts sender from From header
- creates account for sender if one does not exist, based on email
- extracts 'content' of email
- if content is HTML, it is converted to text (quick and dirty, this could be better)
- searches for a ticket with the same Subject as the new email
- if a matching ticket is found, the content is appended to that ticket, attributed to the account
- if not, a new Redmine ticket is created with the email subject and content, attributed to the account
- if the user is blocked (see below), the ticket is created as above, and will immediately be put in a `Reject` state.


## Block system

A "block" system allows messages from certain users be recorded as rejected tickets when processing.

This in initiated when the user is added to a `blocked` team in Redmine. New tickets from users in the `blocked` team are automatically rejected. Updates (new messages using the same subject) from blocked users are still append to rejected ticket, but doesn't change the status of the ticket.

When the `/scn block <email>` command is issued, the user is added to the `blocked` team, then all tickets created by the user are put in a `Reject`ed state. If a user is moved to the `blocked` team without using the Discord command, reject post-action will not trigger (currently). `/scn block` is designed to be idempotent, and multiple calls for the same email should result in the same outcome: user in blocked team and all authored tickets rejected.

The user can be "unblocked" by removing them from the `blocked` team or issuing `/scn unblock <email>`. None of the `Reject`ed tickets will be updated, but new tickets will be created and not rejected.


## Build and Test
A `Makefile` is provided with the following targets:
- `venv` : build a Python virtual environment ("venv") in `.venv`
- `test` : run the unit test suite
- `coverage` : run the unit tests and generate a minimal coverage report
- `htmlcov` : run the unit tests and generate a full report in htmlcov/
- `lint` : generate a pylint report, based on settings in `pylintrc`

All the [`make`](https://en.wikipedia.org/wiki/Make_(software)) targets depend on `venv`, which builds a [standard Python virtual environment](https://docs.python.org/3/library/venv.html).

### Constructing a Virtual Environment

Creating the virtual environment with `make`:
```
make venv
```

If you prefer to do so by hand:
```
python3.11 -m venv .venv
./.venv/bin/pip install --upgrade pip
./.venv/bin/pip install -r requirements.txt
```

`python3.11` is used specificlly as a widely available version. That version can be managed in the `Makefile`.

Any builds targeted with `make` will used the `venv` automatically (and generate it, if it's not already there). Users operating Python from the command line will need to [load their environment](https://docs.python.org/3/library/venv.html#how-venvs-work).

For example, `bash` and `zsh` users would:
```
$ source .venv/bin/activate
```

### Testing

This is a Python project, and tests can be run using Python unittest:
```
python -m unittest
```
or
```
make test
```

Each `test_*.py` file can be run independantly, which targets the specific sub-system and turns on DEBUG logging.

Full integration testing requires standing up a local testbed with the [SCN Redmine](https://github.com/Local-Connectivity-Lab/scn-redmine) container composition. Tests that cannot be run without access to Redmine will skip when the `.env` file is absent.

Automated testing using Github Actions is disabled without valid `.env` settings. *When/if SCN releases public containers*, Github Actions could be updated to run the full integration suite with an `.env` configured for an ephemeral test instance built from the public container.


### Deploying a Redmine Testbed

All the services rely on redmine for testing.

To setup a test instance, follow https://github.com/Local-Connectivity-Lab/scn-redmine/blob/main/README.md

* Deploy scn_redmine
* add custom fields and admin user - covered in Initial Setup (or will be)
* Setup access token in .env as per [redmine](./redmine.md)
File renamed without changes.
Loading

0 comments on commit 499eb82

Please sign in to comment.