summaryrefslogtreecommitdiff
path: root/app/lib/flickr_api/auth.py
blob: 4c3fb9afe4929691969f01778cb34a0233d08c9f (plain)
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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
"""
    Authentication capabilities for the Flickr API.

    It implements the new authentication specifications of Flickr
    based on OAuth.

    The authentication process is in 3 steps.

    - Authorisation request:
    >>> a = AuthHandler(call_back_url)
    >>> a.get_authorization_url('write')
    print  ('http://www.flickr.com/services/oauth/'
            'authorize?oauth_token=xxxx&perms=write')

    - The user gives his authorization at the url given by
    'get_authorization_url' and is redirected to the 'call_back_url' with
    the `oauth_verifier` encoded in the url. This value can then be given to
    the `AuthHandler`:

    >>> a.set_verifier("66455xxxxx")

    - The authorization handler can then be set for the python session
      and will be automatically used when needed.

    >>> flickr_api.set_auth_handler(a)

    The authorization handler can also be saved and loaded:
    >>> a.write(filename)
    >>> a = AuthHandler.load(filename)

    Date: 06/08/2011
    Author: Alexis Mignon <alexis.mignon@gmail.com>
    Author: Christoffer Viken <christoffer@viken.me>

"""

try:  # fixes a reported bug. Seems that oauth can be packed in different ways.
    from oauth import oauth
except ImportError:
    import oauth

import time
import urllib.parse
import urllib.request, urllib.error, urllib.parse
from . import keys

TOKEN_REQUEST_URL = "https://www.flickr.com/services/oauth/request_token"
AUTHORIZE_URL = "https://www.flickr.com/services/oauth/authorize"
ACCESS_TOKEN_URL = "https://www.flickr.com/services/oauth/access_token"

AUTH_HANDLER = None


class AuthHandlerError(Exception):
    pass


class AuthHandler(object):
    def __init__(self, key=None, secret=None, callback=None,
                 access_token_key=None, access_token_secret=None,
                 request_token_key=None, request_token_secret=None):

        self.key = key or keys.API_KEY
        self.secret = secret or keys.API_SECRET

        if self.key is None or self.secret is None:
            raise ValueError("API keys have not been set.")

        if callback is None:
            callback = ("https://api.flickr.com/services/rest/"
                        "?method=flickr.test.echo&api_key=%s" % self.key)

        params = {
            'oauth_timestamp': str(int(time.time())),
            'oauth_signature_method': "HMAC-SHA1",
            'oauth_version': "1.0",
            'oauth_callback': callback,
            'oauth_nonce': oauth.generate_nonce(),
            'oauth_consumer_key': self.key
        }

        self.consumer = oauth.OAuthConsumer(key=self.key, secret=self.secret)
        if (access_token_key is None) and (request_token_key is None):
            req = oauth.OAuthRequest(http_method="GET",
                                     http_url=TOKEN_REQUEST_URL,
                                     parameters=params)
            req.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(),
                             self.consumer, None)
            resp = urllib.request.urlopen(req.to_url())
            request_token = dict(urllib.parse.parse_qsl(resp.read()))
            self.request_token = oauth.OAuthToken(
                request_token['oauth_token'],
                request_token['oauth_token_secret']
            )
            self.access_token = None
        elif request_token_key is not None:
            self.access_token = None
            self.request_token = oauth.OAuthToken(
                request_token_key,
                request_token_secret
            )
        else:
            self.request_token = None
            self.access_token = oauth.OAuthToken(
                access_token_key,
                access_token_secret
            )

    def get_authorization_url(self, perms='read'):
        if self.request_token is None:
            raise AuthHandlerError(
                ("Request token is not defined. This ususally means that the"
                 " access token has been loaded from a file.")
            )
        return "%s?oauth_token=%s&perms=%s" % (
            AUTHORIZE_URL, self.request_token.key, perms
        )

    def set_verifier(self, oauth_verifier):
        if self.request_token is None:
            raise AuthHandlerError(
                ("Request token is not defined. "
                 "This ususally means that the access token has been loaded "
                 "from a file.")
            )
        self.request_token.set_verifier(oauth_verifier)

        access_token_parms = {
            'oauth_consumer_key': self.key,
            'oauth_nonce': oauth.generate_nonce(),
            'oauth_signature_method': "HMAC-SHA1",
            'oauth_timestamp': str(int(time.time())),
            'oauth_token': self.request_token.key,
            'oauth_verifier': self.request_token.verifier
        }

        req = oauth.OAuthRequest(http_method="GET", http_url=ACCESS_TOKEN_URL,
                                 parameters=access_token_parms)
        req.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(),
                         self.consumer, self.request_token)
        resp = urllib.request.urlopen(req.to_url())
        access_token_resp = dict(urllib.parse.parse_qsl(resp.read()))
        self.access_token = oauth.OAuthToken(
            access_token_resp["oauth_token"],
            access_token_resp["oauth_token_secret"]
        )

    def complete_parameters(self, url, params={}, exclude_signature=[]):

        defaults = {
            'oauth_timestamp': str(int(time.time())),
            'oauth_nonce': oauth.generate_nonce(),
            'signature_method': "HMAC-SHA1",
            'oauth_token': self.access_token.key,
            'oauth_consumer_key': self.consumer.key,
        }

        excluded = {}
        for e in exclude_signature:
            excluded[e] = params.pop(e)

        defaults.update(params)
        req = oauth.OAuthRequest(http_method="POST", http_url=url,
                                 parameters=defaults)
        req.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), self.consumer,
                         self.access_token)
        req.parameters.update(excluded)

        return req

    def tofile(self, filename, include_api_keys=False):
        """ saves authentication information to a file.

    Parameters:
    ----------
    filename: str
        The name of the file to which we save the information.

    include_api_keys: bool, optional (default False)
        Should we include the api keys in the file ? For security issues, it
        is recommanded not to save the API keys information in several places
        and the default behaviour is thus not to save the API keys.
"""
        if self.access_token is None:
            raise AuthHandlerError("Access token not set yet.")
        with open(filename, "w") as f:
            if include_api_keys:
                f.write("\n".join([self.key, self.secret,
                        self.access_token.key, self.access_token.secret]))
            else:
                f.write("\n".join([self.access_token.key,
                                   self.access_token.secret]))

    def save(self, filename, include_api_keys=False):
        self.tofile(filename, include_api_keys)

    def write(self, filename, include_api_keys=False):
        self.tofile(filename, include_api_keys)

    def todict(self, include_api_keys=False):
        """
        Dumps the auth object to a dict,
        Optional inclusion of API-keys, in case you are using multiple.
        - include_api_keys: Whether API-keys should be included, False if you
        have control of them.
        """
        if self.access_token is not None:
            dump = {'access_token_key': self.access_token.key,
                    'access_token_secret': self.access_token.secret}
        else:
            dump = {'request_token_key': self.request_token.key,
                    'request_token_secret': self.request_token.secret}
        if include_api_keys:
            dump['api_key'] = self.key
            dump['api_secret'] = self.secret
        return dump

    @staticmethod
    def load(filename, set_api_keys=False):
        """ Load authentication information from a file.

    Parameters
    ----------
    filename: str
        The file in which authentication information is stored.

    set_api_keys: bool, optional (default False)
        If API keys are found in the file, should we use them to set the
        API keys globally.
        Default is False. The API keys should be stored separately from
        authentication information. The recommanded way is to use a
        `flickr_keys.py` file. Setting `set_api_keys=True` should be considered
        as a conveniency only for single user settings.
"""
        return AuthHandler.fromfile(filename, set_api_keys)

    @staticmethod
    def fromfile(filename, set_api_keys=False):
        """ Load authentication information from a file.

    Parameters
    ----------
    filename: str
        The file in which authentication information is stored.

    set_api_keys: bool, optional (default False)
        If API keys are found in the file, should we use them to set the
        API keys globally.
        Default is False. The API keys should be stored separately from
        authentication information. The recommanded way is to use a
        `flickr_keys.py` file. Setting `set_api_keys=True` should be considered
        as a conveniency only for single user settings.
"""
        with open(filename, "r") as f:
            keys_info = f.read().split("\n")
            try:
                key, secret, access_key, access_secret = keys_info
                if set_api_keys:
                    keys.set_keys(api_key=key, api_secret=secret)
            except ValueError:
                access_key, access_secret = keys_info
                key = keys.API_KEY
                secret = keys.API_SECRET
        return AuthHandler(key, secret, access_token_key=access_key,
                           access_token_secret=access_secret)

    @staticmethod
    def fromdict(input_dict):
        """
        Loads an auth object from a dict.
        Structure identical to dict returned by todict
        - input_dict: Dictionary to build from
        """
        access_key, access_secret = None, None
        request_token_key, request_token_secret = None, None
        try:
            if 'api_key' in input_dict:
                key = input_dict['api_key']
                secret = input_dict['api_secret']
            else:
                key = keys.API_KEY
                secret = keys.API_SECRET
            if 'access_token_key' in input_dict:
                access_key = input_dict['access_token_key']
                access_secret = input_dict['access_token_secret']
            elif 'request_token_key' in input_dict:
                request_token_key = input_dict['request_token_key']
                request_token_secret = input_dict['request_token_secret']
        except Exception:
            raise AuthHandlerError("Error occurred while processing data")

        return AuthHandler(key, secret, access_token_key=access_key,
                           access_token_secret=access_secret,
                           request_token_key=request_token_key,
                           request_token_secret=request_token_secret)

    @staticmethod
    def create(access_key, access_secret):
        return AuthHandler(access_token_key=access_key,
                           access_token_secret=access_secret)


def token_factory(filename=None, token_key=None, token_secret=None):
    if filename is None:
        if (token_key is None) or (token_secret is None):
            raise ValueError("token_secret and token_key cannot be None")
        return AuthHandler.create(token_key, token_secret)
    else:
        return AuthHandler.load(filename)


def set_auth_handler(auth_handler, set_api_keys=False):
    """ Set the authentication handler globally.

    Parameters
    ----------
    auth_handler: AuthHandler object or str
        If a string is given, it corresponds to the file in which
        authentication information is stored.

    set_api_keys: bool, optional (default False)
        If API keys are found in the file, should we use them to set the
        API keys globally.
        Default is False. The API keys should be stored separately from
        authentication information. The recommanded way is to use a
        `flickr_keys.py` file. Setting `set_api_keys=True` should be considered
        as a conveniency only for single user settings.
    """
    global AUTH_HANDLER
    if isinstance(auth_handler, str):
        ah = AuthHandler.load(auth_handler, set_api_keys)
        set_auth_handler(ah)
    else:
        AUTH_HANDLER = auth_handler