Skip to content

Commit b8ec3db

Browse files
committed
[Change] Switch authent to use a pre-granted refresh token
1 parent a1519d0 commit b8ec3db

File tree

5 files changed

+44
-83
lines changed

5 files changed

+44
-83
lines changed

.netatmo.credentials

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"CLIENT_ID" : "",
33
"CLIENT_SECRET" : "",
4-
"USERNAME" : "",
5-
"PASSWORD" : ""
4+
"REFRESH_TOKEN" : ""
65
}
76

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ If you are using a single account with a single home and single weather station,
1313
If you have multiple homes or were supplying a station name in some method calls, you will have to adapt your code :
1414
- to supply a home name when looking for data for most class initializers
1515
- to use the new station name set by Netatmo (which is not your previously set value)
16+
17+
18+
>BREAKING CHANGE: Netatmo seems no longer (july 2023) to allow grant_type "password", even for an app credentials that belong to the same account than the home. They have added the capability of creating access_token/refresh_token couple from the dev page (the location where app are created). As a consequence, the username/password credentials can no longer be used and you must replace them with a new parameter REFRESH_TOKEN that you will get from the web interface. To get this token, you are required to specify the scope you want to allow to this token. Select all that apply for your library use.
1619
17-
>Note: Authentication tokens obtained using ClientAuth will always expires after 3 hours. If you are using long lasting sessions, you must renew this tokens by calling again ClientAuth periodically.
20+
>SHORT VERSION TO UPGRADE: If you where using a netatmo_credentials file, juste remove USERNAME and PASSWORD fields and add a REFRESH_TOKEN field which value is the one you will obtain from the https://dev.netatmo.com in MyApps selecting you app and using "Token Generator" after selecting required scopes.
1821
1922
### Install ###
2023

lnetatmo.py

Lines changed: 27 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,16 @@
4545
# Authentication use :
4646
# 1 - Values hard coded in the library
4747
# 2 - The .netatmo.credentials file in JSON format in your home directory
48-
# 3 - Values defined in environment variables : CLIENT_ID, CLIENT_SECRET, USERNAME, PASSWORD
49-
# Note: The USERNAME environment variable may interfer with the envvar used by Windows for login name
50-
# if you have this issue, do not forget to "unset USERNAME" before running your program
48+
# 3 - Values defined in environment variables : CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN
5149

5250
# Each level override values defined in the previous level. You could define CLIENT_ID and CLIENT_SECRET hard coded in the library
53-
# and username/password in .netatmo.credentials or environment variables
51+
# and REFRESH_TOKEN in .netatmo.credentials or environment variables
5452

5553
# 1 : Embedded credentials
5654
cred = { # You can hard code authentication information in the following lines
57-
"CLIENT_ID" : "", # Your client ID from Netatmo app registration at http://dev.netatmo.com/dev/listapps
55+
"CLIENT_ID" : "", # Your client ID from Netatmo app registration at http://dev.netatmo.com
5856
"CLIENT_SECRET" : "", # Your client app secret ' '
59-
"USERNAME" : "", # Your netatmo account username
60-
"PASSWORD" : "" # Your netatmo account password
57+
"REFRESH_TOKEN" : "" # Your scoped refresh token (generated with app credentials)
6158
}
6259

6360
# Other authentication setup management (optionals)
@@ -73,17 +70,9 @@ def getParameter(key, default):
7370
cred.update({k.upper():v for k,v in json.loads(f.read()).items()})
7471

7572
# 3 : Override final value with content of env variables if defined
76-
# Warning, for Windows user, USERNAME contains by default the windows logged user name
77-
# This usually lead to an authentication error
78-
if platform.system() == "Windows" and getenv("USERNAME", None):
79-
warnings.warn("You are running on Windows and the USERNAME env var is set. " \
80-
"Be sure this env var contains Your Netatmo username " \
81-
"or clear it with <SET USERNAME=> before running your program\n", RuntimeWarning, stacklevel=3)
82-
8373
_CLIENT_ID = getParameter("CLIENT_ID", cred)
8474
_CLIENT_SECRET = getParameter("CLIENT_SECRET", cred)
85-
_USERNAME = getParameter("USERNAME", cred)
86-
_PASSWORD = getParameter("PASSWORD", cred)
75+
_REFRESH_TOKEN = getParameter("REFRESH_TOKEN", cred)
8776

8877
#########################################################################
8978

@@ -174,56 +163,38 @@ class ClientAuth:
174163
Args:
175164
clientId (str): Application clientId delivered by Netatmo on dev.netatmo.com
176165
clientSecret (str): Application Secret key delivered by Netatmo on dev.netatmo.com
177-
username (str)
178-
password (str)
179-
scope (Optional[str]): Default value is 'read_station'
180-
read_station: to retrieve weather station data (Getstationsdata, Getmeasure)
181-
read_camera: to retrieve Welcome data (Gethomedata, Getcamerapicture)
182-
access_camera: to access the camera, the videos and the live stream.
183-
Several value can be used at the same time, ie: 'read_station read_camera'
166+
refresh_token (str) : Scoped refresh token
184167
"""
185168

186169
def __init__(self, clientId=_CLIENT_ID,
187170
clientSecret=_CLIENT_SECRET,
188-
username=_USERNAME,
189-
password=_PASSWORD,
190-
scope="read_station read_camera access_camera write_camera " \
191-
"read_presence access_presence write_presence read_thermostat write_thermostat"):
171+
refreshToken=_REFRESH_TOKEN):
192172

193-
postParams = {
194-
"grant_type" : "password",
195-
"client_id" : clientId,
196-
"client_secret" : clientSecret,
197-
"username" : username,
198-
"password" : password,
199-
"scope" : scope
200-
}
201-
resp = postRequest(_AUTH_REQ, postParams)
202-
if not resp: raise AuthFailure("Authentication request rejected")
203-
204173
self._clientId = clientId
205174
self._clientSecret = clientSecret
206-
self._accessToken = resp['access_token']
207-
self.refreshToken = resp['refresh_token']
208-
self._scope = resp['scope']
209-
self.expiration = int(resp['expire_in'] + time.time())
175+
self._accessToken = None
176+
self.refreshToken = refreshToken
177+
self.expiration = 0 # Force refresh token
210178

211179
@property
212180
def accessToken(self):
213-
214-
if self.expiration < time.time(): # Token should be renewed
215-
postParams = {
216-
"grant_type" : "refresh_token",
217-
"refresh_token" : self.refreshToken,
218-
"client_id" : self._clientId,
219-
"client_secret" : self._clientSecret
220-
}
221-
resp = postRequest(_AUTH_REQ, postParams)
222-
self._accessToken = resp['access_token']
223-
self.refreshToken = resp['refresh_token']
224-
self.expiration = int(resp['expire_in'] + time.time())
181+
if self.expiration < time.time() : self.renew_token()
225182
return self._accessToken
226183

184+
def renew_token(self):
185+
postParams = {
186+
"grant_type" : "refresh_token",
187+
"refresh_token" : self.refreshToken,
188+
"client_id" : self._clientId,
189+
"client_secret" : self._clientSecret
190+
}
191+
resp = postRequest(_AUTH_REQ, postParams)
192+
if self.refreshToken != resp['refresh_token']:
193+
print("New refresh token:", resp['refresh_token'])
194+
self._accessToken = resp['access_token']
195+
self.refreshToken = resp['refresh_token']
196+
self.expiration = int(resp['expire_in'] + time.time())
197+
227198

228199
class User:
229200
"""
@@ -774,7 +745,7 @@ def postRequest(url, params=None, timeout=10):
774745
try:
775746
resp = urllib.request.urlopen(req, params, timeout=timeout) if params else urllib.request.urlopen(req, timeout=timeout)
776747
except urllib.error.HTTPError as err:
777-
logger.error("code=%s, reason=%s" % (err.code, err.reason))
748+
logger.error("code=%s, reason=%s, body=%s" % (err.code, err.reason, err.fp.read()))
778749
return None
779750
else:
780751
if params:
@@ -839,7 +810,7 @@ def getStationMinMaxTH(station=None, module=None, home=None):
839810

840811
logging.basicConfig(format='%(name)s - %(levelname)s: %(message)s', level=logging.INFO)
841812

842-
if not _CLIENT_ID or not _CLIENT_SECRET or not _USERNAME or not _PASSWORD :
813+
if not _CLIENT_ID or not _CLIENT_SECRET or not _REFRESH_TOKEN :
843814
stderr.write("Library source missing identification arguments to check lnetatmo.py (user/password/etc...)")
844815
exit(1)
845816

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
setup(
66
name='lnetatmo',
7-
version='2.1.0',
7+
version='3.0.0',
88
classifiers=[
99
'Development Status :: 5 - Production/Stable',
1010
'Intended Audience :: Developers',
@@ -17,7 +17,7 @@
1717
scripts=[],
1818
data_files=[],
1919
url='https://github.com/philippelt/netatmo-api-python',
20-
download_url='https://github.com/philippelt/netatmo-api-python/tarball/v2.1.0.tar.gz',
20+
download_url='https://github.com/philippelt/netatmo-api-python/archive/v3.0.0.tar.gz',
2121
license='GPL V3',
2222
description='Simple API to access Netatmo weather station data from any python script.'
2323
)

usage.md

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ Python Netatmo API programmers guide
1313
1414
>2020-12-07, Breaking changes due to removal of direct access to devices, "home" being now required (Netatmo redesign)
1515
16+
>2023-07-12, Breaking changes due to deprecation of grant_type "password" for ALL apps
17+
1618
No additional library other than standard Python library is required.
1719

1820
Both Python V2.7x and V3.x.x are supported without change.
1921

2022
More information about the Netatmo REST API can be obtained from http://dev.netatmo.com/doc/
2123

22-
This package support only user based authentication.
24+
This package support only preauthenticated scoped tokens created along apps.
2325

2426

2527

@@ -32,6 +34,7 @@ Before being able to use the module you will need :
3234

3335
* A Netatmo user account having access to, at least, one station
3436
* An application registered from the user account (see http://dev.netatmo.com/dev/createapp) to obtain application credentials.
37+
* Create a couple access_token/refresh_token at the same time with your required scope (depending of your intents on library use)
3538

3639
In the netatmo philosophy, both the application itself and the user have to be registered thus have authentication credentials to be able to access any station. Registration is free for both.
3740

@@ -52,24 +55,19 @@ Authentication data can be supplied with 4 different methods (each method overri
5255
{
5356
"CLIENT_ID" : "`xxx",
5457
"CLIENT_SECRET" : "xxx",
55-
"USERNAME" : "xxx",
56-
"PASSWORD" : "xxx"
58+
"REFRESH_TOKEN" : "xxx"
5759
}
5860
$
5961

6062
3. Some or all values can be overriden by environment variables. This is the easiest method if your are packaging your application with Docker. It also allow you to do some testing with other accounts without touching your current ~/.netatmo.credentials file
6163

62-
$ export USERNAME=newUsername
63-
$ export PASSWORD=password
64+
$ export REFRESH_TOKEN="yyy"
6465
$ python3 MyCodeUsingLnetatmo.py
6566
...
6667
67-
**Note to windows users:**
68-
> If you are running on Windows platform, take care to the **USERNAME** environment variable that is automatically set with the windows login user name. This is likely to conflict with the user name you are using for your Netatmo account and will result in an unexpected authentication failure. In such case, take care to "unset" the default **USERNAME** env variable before running your code (or set it with your actual Netatmo account ID).
69-
7068
4. Some or all values can be overriden by explicit call to initializer of ClientAuth class
7169

72-
# Example: USERNAME and PASSWORD supposed to be defined by one of the previous methods
70+
# Example: REFRESH_TOKEN supposed to be defined by one of the previous methods
7371
authData = lnetatmo.ClientAuth( clientId="netatmo-client-id",
7472
clientSecret="secret" )
7573

@@ -151,7 +149,7 @@ The results are Python data structures, mostly dictionaries as they mirror easil
151149
_CLIENT_ID, _CLIENT_SECRET = Application ID and secret provided by Netatmo
152150
application registration in your user account
153151

154-
_USERNAME, _PASSWORD : Username and password of your netatmo account
152+
_REFRESH_TOKEN : Refresh token created along the app client credentials
155153

156154
_BASE_URL and _*_REQ : Various URL to access Netatmo web services. They are
157155
documented in http://dev.netatmo.com/doc/ They should not be changed unless
@@ -169,9 +167,7 @@ Constructor
169167
```python
170168
authorization = lnetatmo.ClientAuth( clientId = _CLIENT_ID,
171169
clientSecret = _CLIENT_SECRET,
172-
username = _USERNAME,
173-
password = _PASSWORD,
174-
scope = "read_station"
170+
refreshToken = _REFRESH_TOKEN,
175171
)
176172
```
177173

@@ -185,17 +181,9 @@ Return : an authorization object that will supply the access token required by o
185181
Properties, all properties are read-only unless specified :
186182

187183
* **accessToken** : Retrieve a valid access token (renewed if necessary)
188-
* **refreshToken** : The token used to renew the access token (normally should not be used)
184+
* **refreshToken** : The token used to renew the access token (normally should not be used explicitely)
189185
* **expiration** : The expiration time (epoch) of the current token
190-
* **scope** : The scope of the required access token (what will it be used for) default to read_station to provide backward compatibility.
191186

192-
Possible values for scope are :
193-
- read_station: to retrieve weather station data (Getstationsdata, Getmeasure)
194-
- read_camera: to retrieve Welcome data (Gethomedata, Getcamerapicture)
195-
- access_camera: to access the camera, the videos and the live stream.
196-
197-
Several value can be used at the same time, ie: 'read_station read_camera'
198-
199187

200188

201189
#### 4-3 User class ####

0 commit comments

Comments
 (0)