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

Async and data integrity protections #5

Merged
merged 9 commits into from
Jul 24, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[flake8]
max-line-length = 99
import-order-style = google
exclude =
env/*
venv/*
accept-encodings = utf-8
ignore = E203
29 changes: 29 additions & 0 deletions .github/workflows/python-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Python lint

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Lint with black
run: |
black . --check
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --statistics
10 changes: 4 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ __pycache__/

# Config file
config.yaml
tokens.csv
rooms.csv
volunteers.csv
volunteer_rooms.csv
presenters.csv
presenter_rooms.csv
*.csv

# Data files
store/

# Log files
*.log
41 changes: 25 additions & 16 deletions bot_actions.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,54 @@
from nio import Api
# coding=utf-8

from hashlib import sha256
def valid_token(token, tokens,sender):
import logging

from nio import Api

logger = logging.getLogger(__name__)


def valid_token(token, tokens, sender):
h = sha256()
h.update(token.encode("utf-8"))
msg = h.hexdigest()
if msg in tokens:
if tokens[msg] == 'unused':
if tokens[msg] == "unused":
return True, msg
elif tokens[msg] == sender:
return True, msg
return False, ""
return False, msg


async def community_invite(client, group, sender):#
async def community_invite(client, group, sender): #
if not group:
return
path = "groups/{}/admin/users/invite/{}".format(group,sender)
data = {"user_id":sender}
path = "groups/{}/admin/users/invite/{}".format(group, sender)
data = {"user_id": sender}
query_parameters = {"access_token": client.access_token}
path = Api._build_path(path, query_parameters)
print(path)
await client.send("PUT",
path,
Api.to_json(data),
headers = {"Content-Type": "application/json"}
)
logging.debug("community_invite path: %r", path)
await client.send(
"PUT", path, Api.to_json(data), headers={"Content-Type": "application/json"}
)
return


def is_admin(user):
user = str(user)
print(user)
logging.debug("is_admin? %s", user)
try:
f = open("admin.csv", "rt")
for nick in f.readlines():
print(nick)
logging.debug("is_admin line: %s", nick)
if user == nick.rstrip():
f.close()
return True
f.close()
except FileNotFoundError:
print("no admin.csv")
logging.error("No admin.csv")
return False


def get_alias(roomid):
return roomid
150 changes: 105 additions & 45 deletions bot_commands.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
# coding=utf-8

from asyncio import Lock
import csv
import logging
from os import fsync, rename

from bot_actions import community_invite, is_admin, valid_token
from chat_functions import send_text_to_room
from bot_actions import valid_token, community_invite, is_admin, get_alias
from nio import RoomResolveAliasResponse

logger = logging.getLogger(__name__)

_attendee_token_lock = Lock()
_presenter_token_lock = Lock()
_volunteer_token_lock = Lock()


class Command(object):
def __init__(self, client, store, config, command, room, event):
"""A command made by a user
Expand All @@ -27,9 +41,9 @@ def __init__(self, client, store, config, command, room, event):
self.event = event
self.args = self.command.split()[1:]


async def process(self):
"""Process the command"""
logging.debug("Got command from %s: %r", self.event.sender, self.command)
trigger = self.command.lower()
if trigger.startswith("help"):
await self._show_help()
Expand All @@ -38,7 +52,7 @@ async def process(self):
elif trigger.startswith("ticket"):
await self._process_request("attendee")
elif trigger.startswith("volunteer"):
#await self._volunteer_request()
# await self._volunteer_request()
await self._process_request("volunteer")
elif trigger.startswith("presenter"):
await self._process_request("presenter")
Expand All @@ -52,98 +66,144 @@ async def process(self):

async def _process_request(self, ticket_type):
"""!h $ticket_type $token"""
if not self.args:
response = "You need to add your token after {}".format(self.command)
if not self.args:
response = (
"Add the ticket code from your email after the command, like this: \n"
f"`{self.command} a1b2c3d4e5...`"
)
await send_text_to_room(self.client, self.room.room_id, response)
return
print("args are:" + " ".join([str(x) for x in self.args]))
print("from: " + self.event.sender)
logging.debug("ticket cmd from %s for %s", self.event.sender, ticket_type)
token = str(self.args[0])
if len(token) != 64:
response = "Token must be 64 characters, check your ticket again or if you have trouble, please send an email to [email protected]"
response = (
"Token must be 64 characters, check your ticket again or if you "
"have trouble, please send an email to [email protected]"
)
await send_text_to_room(self.client, self.room.room_id, response)
return
lock = _attendee_token_lock
tokens = self.config.tokens
rooms = self.config.rooms
group = self.config.community
filename = "tokens.csv"
if ticket_type == "presenter":
lock = _presenter_token_lock
tokens = self.config.presenter_tokens
rooms = self.config.presenter_rooms
group = self.config.presenter_community
filename = "presenters.csv"
print("presenter")
elif ticket_type == "volunteer":
lock = _volunteer_token_lock
tokens = self.config.volunteer_tokens
rooms = self.config.volunteer_rooms
group = self.config.volunteer_community
filename = "volunteers.csv"

valid, h = valid_token(token, tokens,self.event.sender)
if valid:
response = "Verified ticket. You should now be invited to the {} rooms.".format(ticket_type)
await send_text_to_room(self.client, self.room.room_id, response)
print(rooms)
for r in rooms:
await self.client.room_invite(r, self.event.sender)
if tokens[h] == "unused":
await send_text_to_room(self.client, self.room.room_id, "Inviting you to the HOPE community...")
# Make sure other tasks don't interfere with our token[] manipulation or writing
async with lock:
valid, h = valid_token(token, tokens, self.event.sender)
if valid:
response = (
"Verified ticket. You should now be invited to the HOPE "
f"{ticket_type} chat rooms and community."
)
await send_text_to_room(self.client, self.room.room_id, response)
logging.debug("Inviting %s to %s", self.event.sender, ",".join(rooms))
for r in rooms:
await self.client.room_invite(r, self.event.sender)
await community_invite(self.client, group, self.event.sender)
tokens[h] = self.event.sender
with open(filename, 'w') as f:
for key in tokens.keys():
f.write("%s,%s\n"%(key,tokens[key]))
return
else:
response = "This is not a valid token, check your ticket again or email [email protected]"
await send_text_to_room(self.client, self.room.room_id, response)
return

if tokens[h] == "unused":
tokens[h] = self.event.sender
filename_temp = filename + ".atomic"
with open(filename_temp, "w") as f:
csv_writer = csv.writer(f)
csv_writer.writerows(tokens.items())
f.flush()
fsync(f.fileno())
rename(filename_temp, filename)

return
else:
logging.info(
"ticket invalid: %s: %s %s (%s)",
self.event.sender,
ticket_type,
token,
tokens.get(h, "<invalid>"),
)
# notify outside lock block
response = (
"This is not a valid token, check your ticket again or "
"email [email protected]"
)
await send_text_to_room(self.client, self.room.room_id, response)

async def _volunteer_request(self):
response = "Inviting you to the HOPE volunteer rooms..."
await send_text_to_room(self.client, self.room.room_id, response)
for r in self.config.volunteer_rooms:
await self.client.room_invite(r, self.event.sender)
await send_text_to_room(self.client, self.room.room_id, "Inviting you to the HOPE community")
await community_invite(self.client, self.config.volunteer_community, self.event.sender)
return
await send_text_to_room(
self.client, self.room.room_id, "Inviting you to the HOPE community"
)
await community_invite(
self.client, self.config.volunteer_community, self.event.sender
)

async def _show_help(self):
"""Show the help text"""
if not self.args:
text = ("Hello, I'm the HOPE CoreBot! To be invited to the official conference channels message me with `ticket <your-token-here>`. You can see more information (important for presenters) at https://wiki.hope.net/index.php?title=Conference_bot")
text = (
"Hello, I'm the HOPE CoreBot! To be invited to the official "
"conference channels message me with `ticket <your-token-here>`. "
"You can find more information (important for presenters) on the "
"[conference bot wiki](https://wiki.hope.net/index.php?title=Conference_bot)."
)
await send_text_to_room(self.client, self.room.room_id, text)
return

async def _the_planet(self):
text = "HACK THE PLANET https://youtu.be/YV78vobCyIo?t=55"
await send_text_to_room(self.client, self.room.room_id, text)
return

async def _trashing(self):
text = """They\'re TRASHING our rights, man! They\'re
TRASHING the flow of data! They\'re TRASHING!
TRASHING! TRASHING! HACK THE PLANET! HACK
THE PLANET!"""
TRASHING the flow of data! They\'re TRASHING!
TRASHING! TRASHING! HACK THE PLANET! HACK
THE PLANET!"""
await send_text_to_room(self.client, self.room.room_id, text)
return

async def _group(self):
await send_text_to_room(self.client, self.room.room_id, "inviting to group")
await community_invite(self.client, self.config, self.event.sender)

async def _notice(self):
print("notice")
msg = "@room " + " ".join(map(str, self.args[1:]))
logging.warning(
"notice used by %s at %s to send: %r",
self.event.sender,
self.room.room_id,
msg,
)
if len(self.args) < 2:
await send_text_to_room(self.client, self.room.room_id, "notice args: <room-alias\> <strings\>,,,")
await send_text_to_room(
self.client,
self.room.room_id,
"notice args: <room-alias\\> <strings\\>,,,",
)
return
resp = await self.client.room_resolve_alias(self.args[0])
if not isinstance(resp, RoomResolveAliasResponse):
print("bad room alias")
logging.info("notice: bad room alias %s", self.args[0])
await send_text_to_room(
self.client, self.room.room_id, "Invalid room alias"
)
return
room_id = resp.room_id
msg = "@room " + ' '.join(map(str, self.args[1:]))
print("send {} to {}".format(msg,room_id))
await send_text_to_room(self.client, room_id, msg)
return
await send_text_to_room(self.client, self.room.room_id, "Sent")

async def _invite(self):
#invite user to set of rooms
return
# invite user to set of rooms
pass
Loading