Skip to content

Commit

Permalink
Merge branch 'release/v0.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
micheljung committed May 27, 2016
2 parents 0254c95 + 8275e66 commit 47ea545
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ install:
- docker build -t faf-server .

script:
- docker run --link faf-db:db -e FAF_DB_PASSWORD=banana -e COVERALLS_REPO_TOKEN=Vl36DD3XeJI1KjzoyGgxpV3wsVohnJW22 faf-server bash scripts/run_and_report_coverage.sh
- docker run -it --link faf-db:db -e FAF_DB_PASSWORD=banana -e COVERALLS_REPO_TOKEN=Vl36DD3XeJI1KjzoyGgxpV3wsVohnJW22 faf-server bash scripts/run_and_report_coverage.sh
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ WORKDIR /code/
RUN python3.5 -m pip install -e .

# Main entrypoint and the default command that will be run
CMD ["./server.py"]
CMD ["/usr/local/bin/python3.5", "server.py"]

# Game server runs on 8000/tcp, lobby server runs on 8001/tcp, nat echo server runs on 30351/udp
EXPOSE 8000 8001 30351
Expand Down
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,33 @@ master|develop

Install [docker](https://www.docker.com).

Follow the steps to get [faf-db](https://github.com/FAForever/db) setup, the following assumes the db container is called `faf-db`.
Follow the steps to get [faf-db](https://github.com/FAForever/db) setup, the following assumes the db container is called `faf-db` and the database is called `faf_test` and the root password ist `banana`.

docker build -t faf/server .
docker run --link faf-db:db -p 8001:8001 -p 30351:30351 faf/server

The server needs an RSA key to decode uniqueid messages, we've provided an example key in the repo as `faf-server.example.pem`. The server expects this to be named `faf-server.pem` at runtime, so first copy this

cp faf-server.example.pem faf-server.pem

Then use Docker to build and run the server as follows

docker build -t faf-server .
docker run --link faf-db:db -p 8001:8001 -p 30351:30351 faf-server

Check if the container is running with

docker ps

If you cannot find `faf-server`in the list, run `docker run` without `-d` to see what happen.

If you have a different root password, database name then the default (see [config.py](https://github.com/FAForever/server/blob/develop/server/config.py#L43)), you must pass it over the environment parameter of docker, e.g.

docker run --link faf-db:db -p 8001:8001 -p 30351:30351 -e FAF_DB_PASSWORD=<wanted_password> -e FAF_DB_NAME=<db_name> faf-server

## Running the tests

Run `py.test`

docker run --link faf-db:db faf/server bash -c py.test
docker run --link faf-db:db faf-server bash -c py.test

# Contributing

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ aiocron
marisa-trie
oauth2client
git+https://github.com/FAForever/faftools.git@develop#egg=faftools
pyopenssl
1 change: 1 addition & 0 deletions scripts/run_and_report_coverage.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
set -e
py.test --cov-report term-missing --cov=server
coveralls
4 changes: 2 additions & 2 deletions server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
from .ladder_service import LadderService
from .control import init as run_control_server

__version__ = '0.1'
__author__ = 'Chris Kitching, Dragonfire, Gael Honorez, Jeroen De Dauw, Crotalus, Michael Søndergaard'
__version__ = '0.2'
__author__ = 'Chris Kitching, Dragonfire, Gael Honorez, Jeroen De Dauw, Crotalus, Michael Søndergaard, Michel Jung'
__contact__ = '[email protected]'
__license__ = 'GPLv3'
__copyright__ = 'Copyright (c) 2011-2015 ' + __author__
Expand Down
14 changes: 7 additions & 7 deletions server/api/api_accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@

class ApiAccessor:
def __init__(self):
with open("faf-server.pem", "rb") as f:
self.private_key = f.read()
self._service_account_credentials = ServiceAccountCredentials.from_p12_keyfile(
'faf-server',
'faf-server.pem',
scopes='write_achievements write_events'
)
self._service_account_credentials.token_uri = API_TOKEN_URI

async def api_get(self, path, player_id):
loop = asyncio.get_event_loop()
Expand All @@ -26,9 +30,5 @@ async def api_post(self, path, player_id, data=None, headers=None):
return result

def http(self, sub=None):
credentials = ServiceAccountCredentials.from_p12_keyfile(
'faf-server',
'faf-server.pem',
scopes='write_achievements write_events'
)
credentials = self._service_account_credentials.create_delegated(sub)
return credentials.authorize(Http())
83 changes: 55 additions & 28 deletions server/lobbyconnection.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@ async def send_coop_maps(self):
jsonToSend["type"] = "Cybran Vanilla Campaign"
elif type == 3:
jsonToSend["type"] = "UEF Vanilla Campaign"
elif type == 4:
jsonToSend["type"] = "Custom Missions"
else:
# Don't sent corrupt data to the client...
self._logger.error("Unknown coop type!")
Expand Down Expand Up @@ -528,39 +530,64 @@ def decodeUniqueId(self, serialized_uniqueid):
encoded = message[24:-40]
key = (base64.b64decode(message[-40:]))

# The JSON string is AES encrypted
# first decrypt the AES key with our rsa private key
AESkey = rsa.decrypt(key, PRIVATE_KEY)

# What the hell is this?
# now decrypt the message
cipher = AES.new(AESkey, AES.MODE_CBC, iv)
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).decode('utf-8')
decoded = DecodeAES(cipher, encoded)[:-trailing]
regexp = re.compile(r'[0-9a-zA-Z\\]("")')
decoded = regexp.sub('"', decoded)
decoded = decoded.replace("\\", "\\\\")
regexp = re.compile('[^\x09\x0A\x0D\x20-\x7F]')
decoded = regexp.sub('', decoded)
jstring = json.loads(decoded)

if str(jstring["session"]) != str(self.session) :
self.sendJSON(dict(command="notice", style="error", text="Your session is corrupted. Try relogging"))
return None

machine = jstring["machine"]

UUID = str(machine.get('UUID', 0)).encode()
mem_SerialNumber = str(machine.get('mem_SerialNumber', 0)).encode()
DeviceID = str(machine.get('DeviceID', 0)).encode()
Manufacturer = str(machine.get('Manufacturer', 0)).encode()
Name = str(machine.get('Name', 0)).encode()
ProcessorId = str(machine.get('ProcessorId', 0)).encode()
SMBIOSBIOSVersion = str(machine.get('SMBIOSBIOSVersion', 0)).encode()
SerialNumber = str(machine.get('SerialNumber', 0)).encode()
VolumeSerialNumber = str(machine.get('VolumeSerialNumber', 0)).encode()

for i in machine.values() :
low = i.lower()
if "vmware" in low or "virtual" in low or "innotek" in low or "qemu" in low or "parallels" in low or "bochs" in low :
return "VM"

# since the legacy uid.dll generated JSON is flawed,
# there's a new JSON format, starting with '2' as magic byte
if decoded.startswith('2'):
data = json.loads(decoded[1:])
if str(data['session']) != str(self.session) :
self.sendJSON(dict(command="notice", style="error", text="Your session is corrupted. Try relogging"))
return None
# We're bound to generate to _old_ hashes from the new JSON structure,
# so we still use hashlib.md5().update() to generate the MD5 hash from concatenated bytearrays.
# Therefore all needed JSON elements are converted to strings and encoded to bytearrays.
UUID = str(data['machine']['uuid']).encode()
mem_SerialNumber = str(data['machine']['memory']['serial0']).encode()
DeviceID = str(data['machine']['disks']['controller_id']).encode()
Manufacturer = str(data['machine']['bios']['manufacturer']).encode()
Name = str(data['machine']['processor']['name']).encode()
ProcessorId = str(data['machine']['processor']['id']).encode()
SMBIOSBIOSVersion = str(data['machine']['bios']['smbbversion']).encode()
SerialNumber = str(data['machine']['bios']['serial']).encode()
VolumeSerialNumber = str(data['machine']['disks']['vserial']).encode()
else:
# the old JSON format contains unescaped backspaces in the device id
# of the IDE controller, which now needs to be corrected to get valid JSON
regexp = re.compile(r'[0-9a-zA-Z\\]("")')
decoded = regexp.sub('"', decoded)
decoded = decoded.replace("\\", "\\\\")
regexp = re.compile('[^\x09\x0A\x0D\x20-\x7F]')
decoded = regexp.sub('', decoded)
jstring = json.loads(decoded)

if str(jstring["session"]) != str(self.session) :
self.sendJSON(dict(command="notice", style="error", text="Your session is corrupted. Try relogging"))
return None

machine = jstring["machine"]

UUID = str(machine.get('UUID', 0)).encode()
mem_SerialNumber = str(machine.get('mem_SerialNumber', 0)).encode() # serial number of first memory module
DeviceID = str(machine.get('DeviceID', 0)).encode() # device id of the IDE controller
Manufacturer = str(machine.get('Manufacturer', 0)).encode() # BIOS manufacturer
Name = str(machine.get('Name', 0)).encode() # verbose processor name
ProcessorId = str(machine.get('ProcessorId', 0)).encode()
SMBIOSBIOSVersion = str(machine.get('SMBIOSBIOSVersion', 0)).encode()
SerialNumber = str(machine.get('SerialNumber', 0)).encode() # BIOS serial number
VolumeSerialNumber = str(machine.get('VolumeSerialNumber', 0)).encode() # https://www.raymond.cc/blog/changing-or-spoofing-hard-disk-hardware-serial-number-and-volume-id/

for i in machine.values() :
low = i.lower()
if "vmware" in low or "virtual" in low or "innotek" in low or "qemu" in low or "parallels" in low or "bochs" in low :
return "VM"

m = hashlib.md5()
m.update(UUID + mem_SerialNumber + DeviceID + Manufacturer + Name + ProcessorId + SMBIOSBIOSVersion + SerialNumber + VolumeSerialNumber)
Expand Down
6 changes: 3 additions & 3 deletions tests/data/db-fixtures.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use faf_test;

-- Login table
delete from login;
insert into login (id, login, email, password) values (1, 'test', '[email protected]', 'test_password');
insert into login (id, login, email, password) values (2, 'Dostya', '[email protected]', 'vodka');
insert into login (id, login, email, password) values (3, 'Rhiza', '[email protected]', 'puff_the_magic_dragon');
insert into login (id, login, email, password) values (1, 'test', '[email protected]', SHA2('test_password', 256));
insert into login (id, login, email, password) values (2, 'Dostya', '[email protected]', SHA2('vodka', 256));
insert into login (id, login, email, password) values (3, 'Rhiza', '[email protected]', SHA2('puff_the_magic_dragon', 256));

-- global rating
delete from global_rating;
Expand Down
4 changes: 3 additions & 1 deletion tests/integration_tests/test_server.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
import hashlib

from server import VisibilityState

Expand Down Expand Up @@ -44,11 +45,12 @@ def get_session(proto):
@asyncio.coroutine
def perform_login(proto, credentials):
login, pw = credentials
pw_hash = hashlib.sha256(pw.encode('utf-8'))
proto.send_message({'command': 'hello',
'version': '1.0.0-dev',
'user_agent': 'faf-client',
'login': login,
'password': pw,
'password': pw_hash.hexdigest(),
'unique_id': 'some_id'
})
yield from proto.drain()
Expand Down

0 comments on commit 47ea545

Please sign in to comment.