Completed
Push — master ( 7beec5...72ed44 )
by Marek
17s queued 14s
created

ige.ClientMngr.ClientMngr._initAdminAccount()   A

Complexity

Conditions 3

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nop 1
dl 0
loc 15
rs 9.85
c 0
b 0
f 0
1
#
2
#  Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/]
3
#
4
#  This file is part of Outer Space.
5
#
6
#  Outer Space is free software; you can redistribute it and/or modify
7
#  it under the terms of the GNU General Public License as published by
8
#  the Free Software Foundation; either version 2 of the License, or
9
#  (at your option) any later version.
10
#
11
#  Outer Space is distributed in the hope that it will be useful,
12
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#  GNU General Public License for more details.
15
#
16
#  You should have received a copy of the GNU General Public License
17
#  along with Outer Space; if not, write to the Free Software
18
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
#
20
21
import os
22
import hashlib
23
import random
24
import time
25
import log
26
import ige
27
from ige import SecurityException
28
from ige.Const import ADMIN_LOGIN
29
import Authentication
30
from account import Account, AIAccount
31
from ai_parser import AIList
32
from IDataHolder import IDataHolder
33
34
class ClientMngr:
35
36
    def __init__(self, database, authMethod, configDir):
37
        self.configDir = configDir
38
        self.authMethod = authMethod
39
        if not self.authMethod:
40
            self.authMethod = Authentication.defaultMethod
41
        if ige.igeRuntimeMode == 1:
42
            Authentication.init(configDir, self.authMethod, 2048)
43
        elif ige.igeRuntimeMode == 0:
44
            # it is minimum to cater for AI generated passwords
45
            Authentication.init(configDir, self.authMethod, 512)
46
        self._filename = os.path.join(self.configDir, 'accounts')
47
        self.sessions = {}
48
        #
49
        self.accounts = database
50
        self._initAdminAccount()
51
        self.generateAIList()
52
53
    def shutdown(self):
54
        self.accounts.shutdown()
55
56
    def checkpoint(self):
57
        self.accounts.checkpoint()
58
59
    def backup(self, basename):
60
        self.accounts.backup(basename)
61
62
    def exists(self, login):
63
        return self.accounts.has_key(str(login))
64
65
    def __getitem__(self, login):
66
        return self.accounts[str(login)]
67
68
    def _initAdminAccount(self):
69
        # create special key
70
        password = hashlib.sha1(str(random.randrange(0, 1e10))).hexdigest()
71
        if self.accounts.has_key(ADMIN_LOGIN):
72
            self.accounts[ADMIN_LOGIN].passwdHash = None # Needs plaintext login from token
73
            self.accounts[ADMIN_LOGIN].passwd = self.accounts[ADMIN_LOGIN].hashPassword(password)
74
        else:
75
            log.message("No administator account found! (looking for '%s')" % ADMIN_LOGIN)
76
            log.message("Creating default account")
77
            # create account
78
            account = Account(ADMIN_LOGIN, "Administrator", "[email protected]", password, passwdHash = None)
79
            # update
80
            self.accounts.create(account, id = str(account.login))
81
        with open(os.path.join(self.configDir, "token"), "w") as tokenFile:
82
            tokenFile.write(password)
83
84
    def createAccount(self, sid, login, safePasswd, nick, email):
85
        log.message('Creating account', login, nick, email)
86
        session = self.getSession(sid)
87
        plainPassword = Authentication.unwrapUserPassword(safePasswd, session.challenge)
88
        login = login.strip()
89
        plainPassword = plainPassword.strip()
90
        nick = nick.strip()
91
        # check requirement
92
        if len(login) < ige.Const.ACCOUNT_LOGIN_MIN_LEN:
93
            raise SecurityException('Login is too short.')
94
        if len(plainPassword) < ige.Const.ACCOUNT_PASSWD_MIN_LEN:
95
            raise SecurityException('Password is too short.')
96
        if len(nick) < ige.Const.ACCOUNT_NICK_MIN_LEN:
97
            raise SecurityException('Nick is too short.')
98
        # check login, nick and uid
99
        for key in self.accounts.keys():
100
            account = self.accounts[key]
101
            if account.login == login:
102
                raise SecurityException('Login already used.')
103
            elif account.nick == nick:
104
                raise SecurityException('Nick already used.')
105
            elif account.email == email:
106
                raise SecurityException('E-mail already used.')
107
        # create account
108
        account = Account(login, nick, email, plainPassword)
109
        # update
110
        self.accounts.create(account, id = str(account.login))
111
        log.message('Account created, confirmation token:', account.confToken)
112
        # TODO send confirmation token to the email address
113
        return 1, None
114
115
    def createAIAccount(self, login, nick, aiType):
116
        if self.accounts.has_key(login):
117
            log.message('AI account already exists, no work needed.', login, nick)
118
            password = self.accounts[login].passwd
119
            return password, None
120
121
        log.message('Creating AI account', login, nick)
122
        login = login.strip()
123
        nick = nick.strip()
124
        # create account
125
        account = AIAccount(login, nick, aiType)
126
        # update
127
        self.accounts.create(account, id = str(account.login))
128
        log.message('AI account created')
129
        self.generateAIList()
130
        return 1, None
131
132
    def generateAIList(self):
133
        aiList = AIList(self.configDir)
134
        aiList.removeAll()
135
        for login in self.accounts.keys():
136
            account = self.accounts[login]
137
            if not account.isAI:
138
                continue
139
            aiList.add(login, account.passwd, account.aiType)
140
        aiList.save()
141
        log.message('AI list regenerated')
142
143
    def removeAIAccount(self, login):
144
        self.accounts.delete(login)
145
        self.generateAIList()
146
        return 1, None
147
148
    def resetAIAccounts(self):
149
        for login in self.accounts.keys():
150
            account = self.accounts[login]
151
            if account.isAI:
152
                self.accounts.delete(login)
153
        self.generateAIList()
154
        return 1, None
155
156
    def hello(self, sid, clientId):
157
        log.debug(clientId, 'connected. User', repr(clientId))
158
        # create sort of cookie
159
        while 1:
160
            sid = hashlib.sha256(str(random.random())).hexdigest()
161
            if not self.sessions.has_key(sid):
162
                break
163
        challenge = Authentication.getWelcomeString(self.authMethod)
164
        session = Session(sid)
165
        session.challenge = challenge
166
        session.clientIdent = clientId
167
        self.sessions[sid] = session
168
        return (sid, challenge), None
169
170
    def login(self, sid, login, cpasswd, hostID):
171
        login = login.strip()
172
        if not login:
173
            raise SecurityException("Specify login, please.")
174
        if login in ():
175
            raise SecurityException("Account blocked")
176
        log.debug(sid, 'login', repr(login), 'hostid', hostID)
177
        login = str(login)
178
        challenge = self.sessions[sid].challenge
179
        log.debug("Trying local login for user", login)
180
        if not self.accounts.has_key(login):
181
            raise SecurityException('Wrong login and/or password.')
182
        account = self.accounts[login]
183
        if not Authentication.verify(cpasswd, account, challenge):
184
            raise SecurityException('Wrong login and/or password.')
185
        # setup session
186
        self.sessions[sid].setAttrs(account.login, account.nick, account.email)
187
        account.lastLogin = time.time()
188
        account.addHostID(hostID)
189
        return 1, None
190
191
    def getAccountData(self, sid):
192
        session = self.getSession(sid)
193
        result = IDataHolder()
194
        result.login = session.login
195
        result.nick = session.nick
196
        result.email = session.email
197
        return result, None
198
199
    def getSession(self, sid):
200
        session = self.sessions.get(sid, None)
201
        if not session:
202
            raise SecurityException('No such session id.')
203
        return session
204
205
    def getSessionByCID(self, cid):
206
        # TODO more effective - used by GameMngr.sendMessage
207
        for sid in self.sessions.keys():
208
            session = self.sessions[sid]
209
            if session.cid == cid:
210
                return session
211
        return None
212
213
    def logout(self, sid):
214
        session = self.sessions.get(sid, None)
215
        if session:
216
            try:
217
                log.debug(sid, 'logout', self.sessions[sid].login)
218
            except AttributeError:
219
                pass
220
            del self.sessions[sid]
221
            return 1, None
222
        else:
223
            raise SecurityException('No such session id.')
224
225
    def changePassword(self, sid, safeOld, safeNew):
226
        session = self.sessions[sid]
227
        if not session:
228
            raise SecurityException('No such session id.')
229
        challenge = session.challenge
230
        account = self.accounts[session.login]
231
        if not Authentication.verify(safeOld, account, challenge):
232
            raise SecurityException('Wrong login and/or password.')
233
        newPassword = Authentication.unwrapUserPassword(safeNew, challenge)
234
        if len(newPassword) < ige.Const.ACCOUNT_PASSWD_MIN_LEN:
235
            raise SecurityException('Password is too short.')
236
        account.passwd = account.hashPassword(newPassword)
237
        log.debug('Password of account {0} successfully changed.'.format(session.login))
238
        return None, None
239
240
    def cleanupSessions(self, sid):
241
        session = self.sessions.get(sid, None)
242
        if session:
243
            log.debug('cleaning up sessions')
244
            now = time.time()
245
            deleted = 0
246
            for id in self.sessions.keys():
247
                if self.sessions[id].timeout < now:
248
                    log.debug("Deleting session", self.sessions[id].sid, getattr(self.sessions[id], "login", "<unknown>"))
249
                    del self.sessions[id]
250
                    deleted += 1
251
            log.debug('cleanup finished (%d active, %d deleted)' % (len(self.sessions), deleted))
252
            return None, None
253
        else:
254
            raise SecurityException('No such session id.')
255
256
    def exportAccounts(self, sid):
257
        # check admin
258
        session = self.getSession(sid)
259
        if session.login != ADMIN_LOGIN:
260
            raise SecurityException('You cannot issue this command.')
261
        # export accounts
262
        f = open(os.path.join(self.configDir,"accounts.txt"), "w")
263
        for account in self.accounts.keys():
264
            account = self.accounts[account]
265
            print >>f, "%s\t%s\t%s\t%s" % (
266
                account.nick.encode("utf-8"),
267
                account.login.encode("utf-8"),
268
                account.passwd.encode("utf-8"),
269
                account.email.encode("utf-8")
270
            )
271
        f.close()
272
        return None, None
273
274
    def serverShutdown(self, sid):
275
        # check admin
276
        session = self.getSession(sid)
277
        if session.login != ADMIN_LOGIN:
278
            raise SecurityException('You cannot issue this command.')
279
        log.message('Shutting down server')
280
        import ige.RPCServer
281
        ige.RPCServer.running = 0
282
        return 1, None
283
284
    # new rpc interface wrappers
285
    def rpc_hello(self, login, clientId):
286
        return self.hello(None, login, clientId)[0]
287
288
    def rpc_login(self, sid, login, cpasswd, hostID):
289
        return self.login(sid, login, cpasswd, hostID)[0]
290
291
    def rpc_logout(self, sid):
292
        return self.logout(sid)[0]
293
294
    def rpc_createAccount(self, sid, login, passwd, nick, email):
295
        return self.createAccount(sid, login, passwd, nick, email)[0]
296
297
# Session class keeps various session data
298
class Session:
299
300
    def __init__(self, sid):
301
        self.cid = None
302
        self.sid = sid
303
        self.messages = {}
304
        self.touch()
305
306
    def setAttrs(self, login, nick, email):
307
        self.login = login
308
        self.nick = nick
309
        self.email = email
310
311
    def touch(self):
312
        # 10 minutes timeout of session
313
        self.timeout = time.time() + 10 * 60
314
315
class Mapping(dict):
316
    pass
317