Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

"""Handle auth and authz activities in bookie""" 

import logging 

 

from decorator import decorator 

from pyramid.decorator import reify 

from pyramid.httpexceptions import HTTPForbidden 

from pyramid.httpexceptions import HTTPFound 

from pyramid.request import Request 

from pyramid.security import unauthenticated_userid 

 

from bookie.models.auth import UserMgr 

 

LOG = logging.getLogger(__name__) 

 

 

class AuthHelper(object): 

    """Manage the inner workings of authorizing things""" 

 

    @staticmethod 

    def check_api(submitted_key, users_key): 

        """Verify the api key is valid""" 

        if users_key != submitted_key: 

            return False 

        else: 

            return True 

 

    @staticmethod 

    def check_login(request, username=None): 

        """Check that the user is logged in correctly 

 

        :param username: a username to make sure the current user is in fact 

 

        """ 

        if request.user is None: 

            return False 

 

        # if we have a username we're told to check against, make sure the 

        # username matches 

        if username is not None and username != request.user.username: 

            return False 

 

        return True 

 

    @staticmethod 

    def not_valid(request, redirect=None): 

        """Handle the Forbidden exception unless redirect is there 

 

        The idea is that if there's a redirect we shoot them to the login form 

        instead 

 

        """ 

        if redirect is None: 

            raise HTTPForbidden('Deactivated Account') 

        else: 

            raise HTTPFound(location=request.route_url(redirect)) 

 

 

class ReqOrApiAuthorize(object): 

    """A context manager that works with either Api key or logged in user""" 

 

    def __init__(self, request, api_key, user_acct, username=None, 

                 redirect=None): 

        self.request = request 

        self.api_key = api_key 

        self.user_acct = user_acct 

        self.username = username 

 

        if redirect: 

            self.redirect = redirect 

 

    def __enter__(self): 

        """Handle the verification side 

 

        Logged in user checked first, then api matching 

 

        """ 

 

        # if the user account is not activated then no go 

        if not self.user_acct.activated: 

            raise HTTPForbidden('Deactivated Account') 

 

        if AuthHelper.check_login(self.request, username=self.username): 

            return True 

 

        if AuthHelper.check_api(self.api_key, self.user_acct.api_key): 

            return True 

 

        raise HTTPForbidden('Invalid Authorization') 

 

    def __exit__(self, exc_type, exc_value, traceback): 

        """No cleanup to do here""" 

        pass 

 

 

class ApiAuthorize(object): 

    """Context manager to check if the user is authorized 

 

    use: 

        with ApiAuthorize(some_key): 

            # do work 

 

    Will return NotAuthorized if it fails 

 

    """ 

 

    def __init__(self, user, submitted_key, redirect=None): 

        """Create the context manager""" 

        self.user = user 

 

 

class RequestWithUserAttribute(Request): 

    @reify 

    def user(self): 

        # <your database connection, however you get it, the below line 

        # is just an example> 

        # dbconn = self.registry.settings['dbconn'] 

        user_id = unauthenticated_userid(self) 

        if user_id is not None: 

            # this should return None if the user doesn't exist 

            # in the database 

            user = UserMgr.get(user_id=user_id) 

            return user 

 

    def __enter__(self): 

        """Verify api key set in constructor""" 

        # if the user account is not activated then no go 

        if not self.user.activated: 

            raise HTTPForbidden('Deactivated Account') 

 

        if not AuthHelper.check_api(self.check_key, self.user.api_key): 

            raise HTTPForbidden('Invalid Authorization') 

 

    def __exit__(self, exc_type, exc_value, traceback): 

        """No cleanup work to do after usage""" 

        pass 

 

 

class ReqAuthorize(object): 

    """Context manager to check if the user is logged in 

 

    use: 

        with ReqAuthorize(request): 

            # do work 

 

    Will return NotAuthorized if it fails 

 

    """ 

 

    def __init__(self, request, username=None, redirect=None): 

        """Create the context manager""" 

        self.request = request 

        self.username = username 

        self.redirect = redirect 

 

    def __enter__(self): 

        """Verify api key set in constructor""" 

        if not AuthHelper.check_login(self.request, self.username): 

            raise HTTPForbidden('Invalid Authorization') 

 

    def __exit__(self, exc_type, exc_value, traceback): 

        """No cleanup work to do after usage""" 

        pass 

 

 

class RequestWithUserAttribute(Request): 

    @reify 

    def user(self): 

        # <your database connection, however you get it, the below line 

        # is just an example> 

        # dbconn = self.registry.settings['dbconn'] 

        user_id = unauthenticated_userid(self) 

        if user_id is not None: 

            # this should return None if the user doesn't exist 

            # in the database 

            user = UserMgr.get(user_id=user_id) 

            return user 

 

 

class api_auth(): 

    """View decorator to set check the client is permitted 

 

    Since api calls can come from the api via a api_key or a logged in user via 

    the website, we need to check/authorize both 

 

    If this is an api call and the api key is valid, stick the user object 

    found onto the request.user so that the view can find it there in one 

    place. 

 

    """ 

 

    def __init__(self, api_field, user_fetcher, admin_only=False, anon=False): 

        """ 

        :param api_field: the name of the data in the request.params and the 

                          User object we compare to make sure they match 

        :param user_fetcher: a callable that I can give a username to and 

                             get back the user object 

 

        :sample: @ApiAuth('api_key', UserMgr.get) 

 

        """ 

        self.api_field = api_field 

        self.user_fetcher = user_fetcher 

        self.admin_only = admin_only 

        self.anon = anon 

 

    def __call__(self, action_): 

        """ Return :meth:`wrap_action` as the decorator for ``action_``. """ 

        return decorator(self.wrap_action, action_) 

 

    def _check_admin_only(self, request): 

        """If admin only, verify current api belongs to an admin user""" 

        api_key = request.params.get(self.api_field, None) 

 

        if request.user is None: 

            user = self.user_fetcher(api_key=api_key) 

        else: 

            user = request.user 

 

        if user is not None and user.is_admin: 

            request.user = user 

            return True 

 

    def wrap_action(self, action_, *args, **kwargs): 

        """ 

        Wrap the controller action ``action_``. 

 

        :param action_: The controller action to be wrapped. 

 

        ``args`` and ``kwargs`` are the positional and named arguments which 

        will be passed to ``action_`` when called. 

 

        """ 

        # check request.user to see if this is a logged in user 

        # if so, then make sure it matches the matchdict user 

 

        # request should be the one and only arg to the view function 

        request = args[0] 

        username = request.matchdict.get('username', None) 

        api_key = None 

 

        # if this is admin only, you're either an admin or not 

        if self.admin_only: 

            if self._check_admin_only(request): 

                return action_(*args, **kwargs) 

            else: 

                request.response.status_int = 403 

                return {'error': "Not authorized for request."} 

 

        if request.user is not None: 

            if AuthHelper.check_login(request, username): 

                # then we're good, this is a valid user for this url 

                return action_(*args, **kwargs) 

 

        # get the user the api key belongs to 

        if self.api_field in request.params: 

            # we've got a request with url params 

            api_key = request.params.get(self.api_field, None) 

            username = request.params.get('username', username) 

 

        def is_json_auth_request(request): 

            if hasattr(request, 'json_body'): 

                if self.api_field in request.json_body: 

                    return True 

            return False 

 

        if is_json_auth_request(request): 

            # we've got a ajax request with post data 

            api_key = request.json_body.get(self.api_field, None) 

            username = request.json_body.get('username', None) 

 

        if username is not None and api_key is not None: 

            # now get what this user should be based on the api_key 

            request.user = self.user_fetcher(api_key=api_key) 

 

            # if there's a username in the url (rdict) then make sure the user 

            # the api belongs to is the same as the url. You can't currently 

            # use the api to get info for other users. 

            if request.user and request.user.username == username: 

                return action_(*args, **kwargs) 

 

        # if this api call accepts anon requests then let it through 

        if self.anon: 

            return action_(*args, **kwargs) 

 

        # otherwise, we're done, you're not allowed 

        request.response.status_int = 403 

        return {'error': "Not authorized for request."}