From 79a3c1b24295e9a02d2407f340470442b05295c8 Mon Sep 17 00:00:00 2001 From: Bernd Weymann Date: Tue, 4 Mar 2025 22:08:38 +0100 Subject: [PATCH] [mercedesme] New authorization process (#18342) * change auth process Signed-off-by: Bernd Weymann --- .../org.openhab.binding.mercedesme/README.md | 75 ++--- .../doc/OH-Step0.png | Bin 32961 -> 0 bytes .../doc/OH-Step1.png | Bin 43706 -> 0 bytes .../doc/OH-Step2.png | Bin 20242 -> 0 bytes .../doc/OH-Step3.png | Bin 12784 -> 0 bytes .../mercedesme/internal/Constants.java | 6 +- .../internal/MercedesMeHandlerFactory.java | 9 +- .../internal/config/AccountConfiguration.java | 4 +- .../internal/dto/TokenResponse.java | 4 +- .../internal/handler/AccountHandler.java | 132 +++------ .../internal/handler/VehicleHandler.java | 2 +- .../internal/server/AuthServer.java | 106 ------- .../internal/server/AuthService.java | 271 +++++++----------- .../internal/server/AuthServlet.java | 104 ------- .../internal/server/MBWebsocket.java | 12 +- .../mercedesme/internal/utils/Utils.java | 88 +----- .../resources/OH-INF/config/bridge-config.xml | 15 +- .../OH-INF/i18n/mercedesme.properties | 13 +- .../binding/mercedesme/StatusTests.java | 58 ++-- .../internal/handler/AccountHandlerMock.java | 17 +- .../internal/handler/VehicleHandlerTest.java | 6 + .../test/resources/json/TokenResponse.json | 10 +- 22 files changed, 249 insertions(+), 683 deletions(-) delete mode 100644 bundles/org.openhab.binding.mercedesme/doc/OH-Step0.png delete mode 100644 bundles/org.openhab.binding.mercedesme/doc/OH-Step1.png delete mode 100644 bundles/org.openhab.binding.mercedesme/doc/OH-Step2.png delete mode 100644 bundles/org.openhab.binding.mercedesme/doc/OH-Step3.png delete mode 100644 bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServer.java delete mode 100644 bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServlet.java diff --git a/bundles/org.openhab.binding.mercedesme/README.md b/bundles/org.openhab.binding.mercedesme/README.md index 964ac2c3d02..5096675ece2 100644 --- a/bundles/org.openhab.binding.mercedesme/README.md +++ b/bundles/org.openhab.binding.mercedesme/README.md @@ -7,12 +7,11 @@ This binding provides access to your Mercedes Benz vehicle like _Mercedes Me_ Sm First time users shall follow the following sequence 1. Setup and configure [Bridge](#bridge-configuration) -2. Follow the [Bridge Authorization](#bridge-authorization) process -3. [Discovery](#discovery) shall find now vehicles associated to your account -4. Add your vehicle from discovery and [configure](#thing-configuration) it with correct VIN -5. Connect your desired items in UI or [text-configuration](#full-example) -6. Optional: you can [Discover your Vehicle](#discover-your-vehicle) more deeply -7. In case of problems check [Troubleshooting](#troubleshooting) section +2. [Discovery](#discovery) shall find now vehicles associated to your account +3. Add your vehicle from discovery and [configure](#thing-configuration) it with correct VIN +4. Connect your desired items in UI or [text-configuration](#full-example) +5. Optional: you can [Discover your Vehicle](#discover-your-vehicle) more deeply +6. In case of problems check [Troubleshooting](#troubleshooting) section ## Supported Things @@ -35,14 +34,20 @@ There's no manual discovery! Bridge needs configuration in order to connect properly to your Mercedes Me account. -| Name | Type | Description | Default | Required | Advanced | -|-----------------|---------|-----------------------------------------|-------------|----------|----------| -| email | text | Mercedes Me registered email Address | N/A | yes | no | -| pin | text | Mercedes Me Smartphone App PIN | N/A | no | no | -| region | text | Your region | EU | yes | no | -| refreshInterval | integer | API refresh interval | 15 | yes | no | -| callbackIP | text | IP Address of openHAB Device | N/A | yes | yes | -| callbackPort | integer | Port Number of openHAB Device | N/A | yes | yes | +| Name | Type | Description | Default | Required | +|-------------------|---------|---------------------------------------------|---------------------------|----------| +| email | text | Mercedes Me registered email Address | N/A | yes | +| refreshToken | text | Refresh Token from MB Token Requester app | takeover previous token | yes | +| pin | text | Mercedes Me Smartphone App PIN | N/A | no | +| region | text | Your region | EU | yes | +| refreshInterval | integer | API refresh interval | 15 | yes | + +`refreshToken` is needed to get access to your Mercedes Me account. +Users already running this binding can stay on default value `takeover previous token`. +New users need to generate `refreshToken` with [MB Token Requester app]( https://github.com/ReneNulschDE/mbapi2020/wiki/How%E2%80%90to:-create-the-access-and-refresh-token ). +It simulates the Mercedes Me application *only for authorization process* on your computer, **not your openHAB system!** +The generated *refresh token* has to be pasted into the bridge configuration. +The generated *token* can be ignored! Set `region` to your location @@ -63,46 +68,6 @@ Commands protected by PIN - Open / Ventilate Windows - Open / Lift Sunroof -IP `callbackIP` and port `callbackPort` will be auto-detected. -If you're running on server with more than one network interface please select manually. - -### Bridge Authorization - -Authorization is needed to activate the Bridge which is connected to your Mercedes Me Account. -The Bridge will indicate in the status headline if authorization is needed including the URL which needs to be opened in your browser. - -Three steps are needed - -1. Open the mentioned URL like 192.168.x.x:8090/mb-auth -Opening this URL will request a PIN which will be send to your configured email. -Check your Mail Account if you received the PIN. -Click on _Continue_ to proceed with Step 2. - -2. Enter your PIN in the shown field. -Leave GUID as identifier as it is. -Click on _Submit_ button. - -3. Confirmation shall be shown that authorization was successful. - -In case of non successful authorization check your log for errors. -Below screenshots are illustrating the authorization flow. - -### After Bridge Setup - - - -### Authorization Step 1 - - - -### Authorization Step 2 - - - -### Authorization Step 3 - - - ## Thing Configuration | Name | Type | Description | Default | Required | Advanced | @@ -814,7 +779,7 @@ Keep these 3 channels disconnected during normal operation. ### Things file ```java -Bridge mercedesme:account:4711 "Mercedes Me John Doe" [ email="YOUR_MAIL_ADDRESS", region="EU", pin=9876, refreshInterval=15] { +Bridge mercedesme:account:4711 "Mercedes Me John Doe" [ email="YOUR_MAIL_ADDRESS", region="EU", pin=9876, refreshToken="abc", refreshInterval=15] { Thing bev eqa "Mercedes EQA" [ vin="VEHICLE_VIN", batteryCapacity=66.5] } ``` diff --git a/bundles/org.openhab.binding.mercedesme/doc/OH-Step0.png b/bundles/org.openhab.binding.mercedesme/doc/OH-Step0.png deleted file mode 100644 index a5b6c179b3c9373582a52d50f4b77f9e738b2b51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32961 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 z 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 NMM3jP>0`^+{|``zKa>Cf diff --git a/bundles/org.openhab.binding.mercedesme/doc/OH-Step1.png b/bundles/org.openhab.binding.mercedesme/doc/OH-Step1.png deleted file mode 100644 index 9bc764f8ff2e7ec120df000e75562f09aa51f4a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43706 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 Y-j^z_Pd&R26e5MDs_v6YWvkHt0VN8MTmS$7 diff --git a/bundles/org.openhab.binding.mercedesme/doc/OH-Step2.png b/bundles/org.openhab.binding.mercedesme/doc/OH-Step2.png deleted file mode 100644 index 8924125cfd0e91bd188a51d50fbd515cbd2ec39d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20242 zcmeF2Ra9G1)Szi8r7cieD5Vgj#hn&Pa47Cjyg+b=;I66Q?i9D;8r)OdC1`MWcLD(> z{b%N3{x$M6^E7v@yH57G>*gfqoV(@Q`v!kil)`&K`T_$315ZX;Tm=IIlOF@)k-)RZ z=pKt{o*i`a$Vo*?6r*gI?4R(&TtqWI!iS=1?5_BrY7>^aQ^d_mgLYV6!7Lw zNylKcy#*qzoF(xvwo1opd#v=k!cW0Alo;F`B6kC;e=Dx-x&pLlkSqJBs#mQ(dY`eC zO5exZ*C(gXYXez|Z1))G^ROO?+!7dZGLnpFPQ%17m2SMH06xK(?hqE@-9lD}hhs4G z;VoTvvx1|74I!9cmH-ZCtlW#&cuSC+p3W)~40}P)?P+K^Kkv$x!?(eT^46P>A&mM( zBNbRm*V9MnzxyRR01Eq$4~0<(w2Tq=G8pe)|IU9GS@AIf$|530%0V~h#pPwLuCA3b z7#Nw-491x6LZro;ymwSbghA6~KEd@*F*3cS!5CujNp^mrrm?q#zwU`2VLW|G1N@$~ z-EVrnEA&0<>l=e-qDJUj%k*wz@Y*o^vX#GocovLjz_5!VJz9K(@lN``kKE#gfh%zc ztE>3%?rZDK?(8HxY z66%p`#0geN^1ZMBfkNKu(iwinkggtN@yUmvv$j}~d3f|pJkwVR!&cdczCr8~^cYZd1 zGM0R(wW{mV?l8N`by#cFx`AmzZs!Q`VHahlknmf`u$!x^VoI`ro$b^WEb$(>b+C7D zdUk@Jj2Oevy*dwRelmGrEenJ?6gC~+TwW$-c&$ToDMloKg*u)w2qR--O3rP<9{O5e z)XCnau(Pvs1<9zfAwkORks|QPhp;9e6dpIOIW?wEf4jN4lU-}|)y=$o*-_oVyTpsm zwQ=^AhKor08NW8ec}$Oh9b=xuRqUoBo2{8ndrwYiblj!M{Rjp8?oSu;%7Zt(WNt-q{VgUp^@8WSD#EO!^=>|%ZqYR zKw72bS~xd&sWImq`5@MstZ>n2GmqGkn_hJ2Id~|xzgxPWf|&Y#>}3#!-l2jaH?9O; zwqBd(NxJ1NbhvKOp7+Tpk{q6`l&om!}85A;-~E7OD$)tCxdZPFScl2{L;OBFE&21 zory1*z23PwMAE!$JjVJuLS=~_*!XB)o@48AYs69j9Q^0vD^lnL{u(k5p$%fgr!w$I z88*MENPp&8TDr{*BY8Ltl_Z$9dO5`kvBC?s221KM^HUQvTH7_bTTYg9w_e^(b1lK# zdYE-$VkVsRmaZpNku)qqFb^Gj5Va65#Bd`yJj0W%Oy{f^f%pa_GO%yp=ba&*4*Jt4 z<3_(UB-tn-xJ6tDuF#Qgo;%+|`dmAeKO-m4fPQ#(0HdtGq6IQ^^>7{57kcl>ZRp_Z zCfhO$Di~Jz&FyaEyTY~D5BP=^^iiFdYX}y{ShiHon`>J>xoZR81UEed{!Q+mu&rTU z&ky709~x6UW|wUe6Hjw=$lD)z6nbA_r=I`DwJ5doyb8Ui54K(hokhd>HOZ=aC%bwS zj<9{&1WjMK_431=QAVq&=9EcPgCDvT(s%-_1!roc7x{w^yf{^geN}-DtJex6u;z>0 zCA}8U_@Qm~hiNdQc8g(W5WHv5LzdV8%#{L8~oFwh}o3x!>K%-IGf{xKJs>uD*&EwthMFJg3qZl`|N2v?TYGfbi%W*j@bh zvyT=g_s7^L4ksqxQN@E+Q11((gMdVNIO=_D#uDBTcBKC1;UL+$zsV^6y{}>|-bKA1 zoXoB70G5;fFrk@iFkc;P%$6?5mh6Vwr?vHd5pG)TzCV_~3UE+WExyVF9R3m#ExNrb zi_gJjKtai$bQelur&_}t#_1zk-BIL71i25%t_OOzP55vZ9RBWn+GKluyfQ*EJ?OhE z6}Y#k`!J$&e^MAYbWF{^q;7I_WZ^&i&m*xr`)EV=04)bZW#{XIIMhUF(YjMQ&lDe4(I9m40myn0CHppkwK z5ls)o0N<>Xr1-9ftCnciqdR&Z)*`eTiWeTA7y8+>qjNQ;RcM@cLH}zpkqNnPqO_pD zut*{Y6fBHQQY@snyI|@7o#gZInTD8rYTVQU8&5BdKUTkSBOz|^Ps;t)ukN>ys(lC5&r1|{ zGY~EwyXRLTN2hC_Ibp)9`&n0hM+#ckC5HW7o`*d!Pj5xZJUVJl|D|TMdL$mn%SjG9 zA>LD}ynwT>4o^Th4uVZ_d}#*L966DT$E=+@nu9CEc@MODjuk^W<6JS3XFc>gF>9y^ zl)ezkXHo&Qe=xq2uVR}uM~OOUcfK^taL|erzLvg|iHx*A&; zj4ClnP4IRhnA^x?UEdmh<4Ut(rl|vp*$w4fybFE_i{zZ?4vGkvKi8Sq8DE>F*_yQm z1zdVj4^!NS4zg%_^Yibh+-2m+>XZ-iPu`K4jijB0+j;*AmyX`4+8i{a*4-314#XG8 z$z%0YYfTOJOUH9sjXY$~gC*S8-SKc6Tka6tkWG3lH>_Up3mw}h`Zut*QcNVr_%XrF zOItGxv~LgE>etj4cieV?$l`JMZ?3dhq#BJ(q+tKu4`06}0s;m(mlI0xmS*MsP~@bO z-%x;1oNCw8PR09MY~mhyuixr*vRXX!${-WRPni~7nvbQhCFRnu;W6)EQIsavfkiK6 z0Dn)Me7QDg^r4pAFc}Y}H&#LyQ+`dKeIx$R&07o+;yYn~lVG^{0u&90I%tukmeak} zdbpm6w*H+~r+%WZkitBMe*;j8Qn>KBO)1UEXMa1{wtAx8#G~wm6XS;>y6vx{SlxUw zu@Olc?l#Wmab-F^6YS)ze$Pm7MD?zSNkfGICKpxQO?( zimhQdv~@y%EAs89n1SyaPtJ#lY<~SdK0g0$U!r)prW94TnR*;I<0asaN8OXEh&!6o znTHTE_QwMNOSsQg;|DSpe7*dNA7BrMdX?u%=9>2d)RBT7``4L8jTmC7Od|2Fc6@;7 zSl6Q#6B6K<`tbA>8H`N$PC3auXIIL6QReZ?8FsVb;N8s`jc6g{*y6K(o@~$-8-Jvy zu-85}Ot2gOj}Q;*{VBFr^YbHw-ThGC0_G27&AUDZw`jV96>-p8w7SH*4p zyN2S_hd;dekxPXNMG~jptN?IBYP=`aYkWygVq8xs&Uv98`!kJ7iubMPr5vfgMK1S; zsVAtd+upeOnKfEnvkE5Dti0HcJ$-yiS088?P2YbGuh{6nnNI8ShRbL_KJOf~!L zDOBgptVPa~4}GTIjr?GVmdrH|#|-(J1`66*x5x=@irZtf*E`9KV`LYz(^wHnMXpmi z-*0SW0dP6tMZuJq~^O2_B(;Pwzm8iEIGRYoeKiLfV4m6Fbi{_#gP7l1v zSyq~`P>YkdYsnusR`X*hEE+YPLV?-M&X<^d4l9xB=PG$&TWItA+Sx)(A#wHWuItmd zSqOpEUP>#fvkvrnj%j zi_r_g4r^|-3O~X3dJtgVyFVZrS>V0b0S9Q}>i19XMM}heEOEfl1PFtPNRr8>p zsk&E()^@&w3rK^%PGagP2H=F?(S3ptu;r``FGZI7I^C;?aJ84xsrStiq}rUvaPitb z;;g>YWj5Ot`4dL-T_68e?{evMXJIK3@vWJUv(obHJSmjB>UD<0Y@lVV5+t~*&nHm3 zK|K5_*w8@a4J-Fa4t@nf!EH-tYle`+P@^9I(k2Dd@K9I7SF`F!H?(a+8VNe%i0_+rh}z*Q00s>JM|P39l|qF zNEI7Co6?A}wV#`2(XtK0W%N|PyLBpE3N%vn;%NI2cKtEE}GOG{)f;(k|m z>8aUx^l;&RRyeLiB=A8edE05PbX^Z)6f+HEp1)pdyPiP>4tmv}2s?}&v)1B%33Iso zgap;;cioxqk&627FQcIjfL_Qy080N{td8!IrW9XIJA46a@}!_AhGfjyO60& zaVl4ui4v~ABeRT1oc&oGtn`-BS@PQ8tK8Tk0iRt(j}=Y;b%KDIcUHSwlV}f?zBOmso zTQ-*>NV#GZ9&ZP{W1|bWGl)=XHr`8sEM7DvQo`@n0|3{--oZ6r2j_)bGYUyqBf@_E z{yk@hb7M~PJkh~{BpDYt<|69Dv0!|^m4-4N`iq^p;Ay`ja7?C!EPz&*L=snZ6?6sYFS*BsDNCrO`DF+oL?oP8IN z7t{I+-e*A@uxsQnxen}lb@#~k;l@bpWy7vYzS8S{MC-k*%CqTwx}^(pAPtAZYA3DS zSet`(7`YX^$U%vKxOi`$gnkOwaUS zlWLvh#KiKw{`sHM?eL}g=UJ?898H+W%grK?<UeZaGj|&2+2x_orjKa@rHE27^iG0Q4 zZ4J2kE4J^i`&hf3S|%g9=86I<$BxALJM&C9pAto0Uzw%?Gr$e7g!ECTQ}3h4C|{4QQ=(~t zEqBdcJ0>qu#h1~r=c30K)Lm25MS6^_m)@Qa3dh2|sZdYTHoT7Qlx3+%iki=fKt(DE+Snvc5cc2$fcp@khKjw1 zL$9-?#kX%Uw<#Q5d>*_kxfh(rHT5Ct4R>xwtx`*U0AlXVL^T2Hv^(!T-K zcDdC#L~i2eLD;M$b!(1>6)UXIxWig)?tcl7ZEae8PVVd!KQy|#dDBw&J)8#EN{D?F z%kOx54tv9K+%<=4XCahstj(bEWj9$arg@IUA5(H@!^wZrToSuPbJE1I^LWv}0PwKL|Qz{gJ}701R1jcbKMl-1z(I zdoQF&+0IaULqOn%xKuKeq~6idyXoQrl%M1o|R3CO1{{;c3|CAw&a-0s;>rdl^=c zI%~TxE`t+0s=gN!V<&4z?mXsw@yvpahEf@b?l6+r*pw9at?QL&rUKn2*Mqex$~%Xp zMP>%G!!ip300U_Yp^^aLlP=!#&j4N6)eOFxo@5k0RDb`!@2(y&C@Y3AN`Y@*N*KfK44(HduID} zLBvY|^ZN%=1Ri%a#FsYjLC+Pdr7#w9fd#p_Il`;Lu=mN@c=Y#`%g(B>&_hX`{wp*! z_rWwp4?m#*0iF$yGmBr{|Mv%6Py2kEjFei_=b50bu`v3R0)jSV>zqR8H$7u^RD5nU*1AXnOrJX zMDmBUZnc$L-LB{@9W}sEpq>F|`7ftUGG94QgX8oPi;2X_j#tW}vQzsdDD{oWTR@s7 zB41*D(;wU>z5U4T=(*D(huj`NLL1W`nULi}fleT_3~%e&Km~Htp(9u_{cQ7=ZZ&SM zo>ni^CHZd$Ggo=l$c!E)J@`|4%k;T5=X9(>xxmJaR`%&7WexjQABmkeFp8?>nD@74 z^1Ei!xVlKdAD+jz7Qv|-qqrYSMG{dil0X^manhH1f? z5t0`Ko)-`IP0Q_4H$Rtb3?kKMWZ~@&zz*P?WPYo`KNG}Krv=Nt!38w15fF4p%C`0L z+n}60$b!=qKVgdINaT&UP9=Eh)pBI+rS3RRFwBx<>ddb7m<98H+ul z+YL`~0e>rL{bNu?J<*{|3>guCM+vNR5A&n+6 zReaU=n+1rmrF|jZL&wW=ObXW=^ARcc?&+o9y}2mJ`!Uk#`k5gvdTr_o2z1s~LL`B` zU6Dqn)*J!F7jxA9-vn;IuzYzekbXe+@F&CmU9R6!xlB}V9_W{a@{S;U|5emb4s>Vx1phtZ>tq}*3 zIm?T&s!V+MyGReiVT}=&iA2)#O_A8?pl>df33xXMQYB~>MbLB^r}~g7HC03mmd9ol z7(Hx2i3LH*Ttf!l>l!6e*(GaZYRA>h-OvrnaK52Pb5d9Q`Z+hJ4!FRcYm5BQpDxLq zK~@cT>d@GYiSc+f4#J?E+Cib1kp!%(*ctJxhifD&iAtt8P;d zp?`$&*ML~TNR`(5F(0%2blH&XyNr0)&}IUoZqTG{LcsT`DWWQsQ_MAO8M-}WEP-&5 zaajYoSHC99U}`6O)JHC6NY2dbcSune23apc8!wn=S9{4|UwfQP5{{-Ch4ba1ja{d- z<&S}%)eOnjdcr;6yj}%o@=D$Q&Hb#v^vjIqPr`E4sj)IiN^#o4xI)=B;q1e5>Ewv4 zV)YR|-MqK`f=qaaY3@3ugU@4+&Ze$hY=(brAW?7W;*uF1jj^$Q-i=mZ3J`XYx(u>og`USVNvn8MAV?X(tO*Z0V>ZlGH9{o|K43k+5M=uN^SG55NJao2&vXYy$&NW#RfDCfxFeVtqJAAYY%BUQT#m$?dcwHrjd z{V1_0U2X9}nWXB9yz6zgG}w^S^P{?huDuGLw%9fpZo9NT^wX&rmA~m6U-!Y+!w~kutpR+xo0b+drKvT3R`0OIvs`#ju zsl!q?BQ_>gy*j@kFp`{qk)6x^wn)KiD)U19{h4NDXkNLtTf0iJX}E^OoNIRxaa?c{ z-0=ulf`BvQ|DGRusb5E5)INmOCb*z)hsxOkNYVTu>0dguq!pd?Hr8cUP-J}+ZnBU& z)v+LaaOmUFSOYX%J5AWU4(*MpZq#az$}tfMW)+?EO0>zzisFoc0IMk(4=)JeE4F0| zF|ocG)n}ai5Y6>f^$G?&8&EJJ<76 zeV|Cr%>S^^c3BmEuMkob>`XZss#)?X&r?fi-}Z5<5wq8pxRyNu;x%1yTN zH+l`PRZROX&v#2b`W!&}_@Q9~&F1`ERrEF@-Y!RyG=BzlOo~C+*dEABZimJ}ux*Bs2LX{>854 zITHjK>eV!)*+?su_G;Ao=&+g$t%EfAJyFRlTlc$Gh727R3#p~OiC8CS+b75x!L+0b zKkQ2_CQ^T;TA1tev%cJg^Pv%6E#Y=tYW`W@b7H;=^AhZX72^=AV8sDNPtc`VarC}j zOxbG9BGa`R?Z||c`Ob?e_1|EH^vkyce7cSqab>}N7JR#lx?h7do?&2!$xjmih!RVd zw70;e#otId>Kc~V80V)D;a=`J9RxNjKbo?TWPmVPkmyenr-io^kNAH4ASC(emcE}NA#55dK)q#Ji2J|l0kGh`7^$XMIJr=;^j zg_fkESiGhaq)^+~D3%a{hjo+Gx0twl*?3WpH?3=BtW&R3%so!y}d6K|Wf zw;X4RHO%DZHa|tw#BhOH`77>+sQeBL#e#l3b z5_;YKA$}3ebZ_fsyL>3f3+@q}d3WDuM;t!!#`Xog&S6B?6ym1rw8jh$GUp02Z({2O zjhG#iwx>JTXrC^7(|XQw(luy2JJ)?i1b&CMy)d+G%5hc?lGSHYSlmobIKy)sktw!` zD5J~1{5~wxQD?<+Xc!FlueF^v*(+dYpOz-x(Ejw${11uZ} z4F)_>C=F?*)pdB62;Y32j5oFWgK*m2GU;q4N-gkAtf4jPb{4DdoB-;^e1-QY>pwJf z)N5TMr^IA@NrsQ1DW;mW5zUPGuG3=DLF53 zcTF06r`RSE4y}wj?iiKRh6A41>QFOH1isqE(m<=m&*D0frJhbMogp{763m^t7TZUM zR!G%Urp$7`V^c4|*o0S0;msTKx+aK&DFJn3?fhsw@8Jbr=9l6ZJl|@1+bOR6E6c9z zM%U$jWzg~Ct8qK;S)Bit?eYW}gKuquz*&BtKZwwZ0ZX|~pn#nzcz&-$_F&}Q%xrbp zBFIQ}><{gi!?DwaO8wat_p;9_iA9edGxZfZKGshl)spflTybCyw`L!xsoS=&p08>@ zcAvv2ehkT~^|rBb%r2k~zt2%@Q1Xl>V<{A10gR<>UXWQDn!us%FyM*BYN*@|E? z&07BXA)N5qJMh<c947 zX=?bYb83`huflZ}`loG1_uf4n$pSdpwFeK{)sK9v*u|ABzmo;Hgt_O`&jUoaGpX_n#bD{F9l%6uB1yLkZA-tu_}`T47_ zM8Fp8+Lrg7>WNgaWYa1acJVt%m*rec?1iIj-|BJQQRduI5}ku4$qeAMTG#W1dzW%! z@A6)Xa4MFi?2EUC#^2{OZocLja?dcQHDa?rjY=$d3#<59aaisU!@Tt~2Ng^Nvf|)J z6$Zq7N38$>pU-6RfpsT|T0Q=fXH{po`8Xjr_wfY7oIlb!v_{UQ8q%94yASL_8sQQ_ zc{%7kRS#uST^S`-l%p{vJ}j|bU8+>XaxP@|(5jI()(Yz3C0$d7d$qF9QO}33Pf?=i z+z;rQt~XZjQtl*pbs>b_LDP-{@Ppmbxcta30{)+Eyt- zoQErurUQSq+AUt)JCUNXGbHkQ%TY|iHi%uOzt}3F0g6Qn%vrEx1X)_~C~bL0Tmxp{ zthkMLfs?jJqC;8Lm6DfGUzeJeTwsM;Jlk(|T0!i8KF|+yrn;g&uX%xRq93e^s-b(s z*lENw$E?IKzBH~Zz3kj%nW@2qQ+_-4l!3R$A4KNs{B>PtCM!6V4wJ{<{04w@wQQQI zBwO>p{6{z_D0x6(uA6kEqG=ggsxanG9&tSG*ZCCVqquk?;<4=4SzT8rEs?TruVQ6u zvz-^fl>O3ZoaX}c^8Wk3iq?_->QZ8x{D~dt$<1$(s*f&?G2bK^a@7pFO_DdFRxNMb z)ngkqeU?%<#ll!p>4Lo6EX3<)TY2MJET4z{^F>Fau~zu@=O;vkA@9iB0%w)z1fx_d zcq7v~XCtF7?a8KKSO5UChc{+Hp6U6H)t75q|sh_`2DG9xPTG&M-E-vhd!41z8L+3 zB47E?J{0=M75krxjcrlr=zmK4u|B&Al+56$<{zSXxBb);0=7MCf6^fYq%!{1;zWL9 zivF_iKWw7TGtq%7#>b#6ivKC*_)pa1|BL9!`d=ZT{~ME|V`77?DKFbwgo+A(`GWk= z(`lz&Ciem?FzP=`6iZmCXK9u*y%2AC?VL)$>Pu z^AUo%ys>iR;O2o@#Yqje3~o!~I?c@Y;d7M)bi|XjP~}2tV);myi+u3Md$q?HeuN*O zylCgv#NrN;Q^anne~=iJJ){L4+H9p`S$PIp?bEOL@%#5+c*ILXM)s?8GGJu?rwrq9 zZ0@VT%cp_4CK<8e%xUAz0_7`#@d{;%nx(DAZYvS7`W3u}E+7kQ*u2TiIGO!e%lwcO z>He);i8;bOQP_l;sM_gMx()2vGTz>e!v1E1|t_4IcKZMEYs3_N{bWJ9{*|+$k+Dj0tx{W?n zR<9i@2eU&?U6|tgZKeRSd$~G-pwhHl1>z}Q=P-q@y)UJjfyt4R0qzAeI0R~9`^|1T z5dDwjz$%IR=Qh)wUG$?cg6`)q^rQT=D1E{>bdqhfyK<#sIQfi%J(6S2MGh>hO@}Oo z@4UET$Jgehj%1Nq7--oF$JbW%z;lsiHVf2D^TX}C}UurS_b zHl`s`YdaQyMq1kp*?H}`)p2R;T4J5Z%xt`DUH-H7Z9^cuZGW@GQ`>G{^P=>irJraf zw%5P~ah z?ICfSjF>Q3_oFCbJ5@*Ed--H^SKdSS{`gmCSjnT?I8Y$jz^k=}r8d#p(!_LUjfVwhN zdIEPCHe75kqs*{ifA8W16s&><2 zV%yZSQKVH!HSo6NlSWeb3=^A9nP<{~yv9+n$JAjFYhI*{x@V{z4suowR3-!R>1+LQ z@EGZON?z1Ow^Y>kLQHMc@+$S}bF;jg;x)^RreU?amxMa&!gRoavwo<}qEEaPJLdyFkg+oi7J;4;F|Ybl{CI} z_ep8=_@cIIs>s#s`ntIXC?UUBh&b+`=p(PTKzc%Qyd_zpu1oT8E)E&%c+uys8dP`v z^$0o}Bz_s4_H`xBUl^7sSSD6Im1&y>wKr7{FU{K}VVyB!%L%hNe-1ASD#q(pJ1KnL z4E%YLm{DG&wM>!~D@r+-=?Ol-OQ~UUbVokl@822n1heLKX*)(CW2e<>e(TNdSAto_ z?Jc{2t-$zkadyw6e1us^A)%xP#ej;d89)16IOm0WI11aJFwyY*Q15#{CGLHDtL&cb zl+AlyC%KmY4LyY&uP@mq)Oo+7o3L0{ zJ(q&`mOoI*bFQ0oPr!bj%X_-FD1CTbxc_t{|C5abr!c|KD1_RT1(yseuOOx>QGFtq zn;vXLtz(eS&8b+`#s-$y?v(UcWO&w?7*soI3dg3vqpV!ZrXA5?+1HVd8NaD~!w&+9 z;5C$cSiMloMr{=`%CAoBT_1q!Vs%Pr+SAMW>AW7^+>y0WCj2C|JHzqJIbAYBdhMFQ zZ3aH11N^`NCi(5@`u7&r`jN8EdURyv`ifdC@{2YV!J^ z_&)YP4llrz17WnV?4!`6I>!<;TSys=!Pj1;=}Q>yyOEc?>5CaBd$F#O*QY- z-sz?IVa0n}GJO{<(Q5x9zc;`)EUaFf=BXK~zN0c3w!e=r$xs^ zwPVKe-E5^Nn~vD8H?a)KfH3vxgl7=nweFkEZYv7*QODGsyVl)d1xNH`4;qAH50HUe z&6ohR6EZ6=#WcC5HJj>!*bYd}$85oW5?%N4!&%Eh_Ia{Gd2CjME??`V9`00D8=FpI zBi81-7i#)&{9-q3y_=>k{~R|*%YL(*RAU??sKp_bI*8wMz72zP}G>`_vf`_ zBHQn8;b=@^WJ~5#-3ZV0nd;9Y4A$<5nQaaXDcVC)9;xZN2HlvTS7hZ0O9rqmZ0;kw zxg6_5?Av^uzr^V;2qybfCv~qc{=F2lP}&0o^0U`9!7$4mY?D;*V$1X)`7xtr9ITrr z$q|=%i0XKk@TMx=xyn`g@mgC6eb(g z#j=nA)PANL)IdwUbBfiWX)h0%>Kustba9})h8%r0YAR}sk`TbEjy^Vxd`;>hM-gCai+9}DIH<{A~7jvcExUye}oVAR2Hp-Kj3{fFPXyk*B;ZDQ|`mdKloxpO+UB_rvFKm z#w5sV#Lv^3+2z!rPZgKQCZlFHUbl{KR`KorqxcNHf}^c&+R#JBFd&PHXY74TG9UQX zx{tV)9270oktaB6qLn{Q^E6+w#?TbHYap|+@^Xv9sabLC#oX?z_-qx$J>MBQpvGC8 zgM_2%g}gSVe$y{ztJ-ymhM7l=&TXIq-A~jDz~VehJE)KtQQX<;gcFVeeQJ2hWX(-= ziH*bCxj? zCljhnCl@cRapbrY%nnFegN7cc7 zNrj+|0IFhGY;^6cpyGuf2&`>J(W7oxcFyhA{8eX1QJUIq?Y1&hJ=G>(-MPmJNWL!AynCRPf+Ifi)_ms&vv|TGmQMLaut@a!6q0d7WO5mQG=7nA{pfA>)Iag*L%o zTea!yNHWXg2-T@OuA#&3-GzvV`hC%=bpEe#>8VEF)b(0(8R&=J!M^pl1y4;)$orn~ zr6`69kw||FZ~}7sYZn|i6Y8!y$3mVr%4jrI?}rfF+!|1yCIkA05dg)C5k93+pPF0S z{(REj`WEHpa^J%XBrSFt`MDlq>YLrxX3_PVSdHTlWmWy8ob=Cj9x}*;7;hWJwUWoA z&E2~%^rnh-*oAMc0wKHEPn&fpWL`H zC7<0oN*wU|Rt-;*PHW^hgH3YznOr=X%op@@y=sSiifX-c;x7qA6o<6(=e#74<#<35 z@oToSESU-a^z$%3q2EvRiwEkccjpO=1Da4LsVXZc^Zwn*9p@jT-mR>2w+_q-u$|`T zWFcPH)W*ttRGuc8UE=0^zni^bF;Lgbp`-R#m z!ruy<5;h&MZFZ3}tA~%q6EsLU$>JMjVkMr39L!rjHa`lO(wBHsEbD4alr@Cz zpal2DwG?Mh|2(75E=4jR-9podeBCyF1_PunhqO*dN{la6JQp`Ak-{#7CwvErTI7t*ltRoa|*!Uid$f%ufh!m?)<-J&2LuN%)S zygxa07RWdMc!Tj&K!cs`$&5y}uN+IY$2*(J4dd!Fg|X&uSk$w|SoyMgYVc(nuFcB$ z4b#zTsf^#BaZ2~D&D@0AE`B`gb5j-xpZ>%A0f_Lg&DG<;!_*nPc~we>ymZ#%tCDm4 z@_346oMK2XA#3uqNyhcFS8k!qMEi(SyBd#h(i5IZ!JA6z)jY_7!e;xv*LLc}ys_;LZ^oFyz`Q_9XakT8O(Zl5sY^ zThzBh?J7!qaovtx<}x2Ds^lsaU$O+gic-)`Em<>-eKxLQuqgV!|SW8nN%&YwqZf=XAs_Ut|9l(kma`f?olHYoM_ifdy z11vE>sBK=Lnu`*}foC4{=iwYDm$Q^Ms|RO|w&y1tfIj_|!;I6ck(OvV&fP$pU{WuO z-BJmBc-qZTdEAvk$8AMbQkazD#of6JuM~cF!@*JbG&}?*-7kzCs3Xz%b zr~S)@SO82`h99|C-Q+BzxI>{+)fB#E>)X_5A*SkD$9Ii}Hxg~C%GqgD#GuEW_so3Z*Tx$e+8G6Qs768g5Ud;&!O-aP(4;J5!h zocn)vwek1xup*Ers#LEf8Sl|~BC($P4?J`=(>WB42cnHA2Tr(R95pDo$1#2H&-9m#gh=T;JvCCbM;2EH%Sq-ZUkj?14VB+#Z*r@+C8EX0vj3_$rx>pglfHWKDxSTA7C;? za>F3OdJSj_JGA45wb1vV=O;5CU0FgN5k8{@nA6-ZXVl47?&*4=YD{l+T01r4wwp@q zy`k^0W0|zhe&Cbj(EcPEG2wjRb5>0_!@vzn%~^{Q6thpe8y z4(O*UKfD*xsj?sK=`P7P0|tMSo$&(M2bFgT5j9&rT(R)yZuuyqzroKyvov6;-YVdkRhW4Y-GWUxPCWL|O3EA_l24`otl(CDyk zKMdfNy$ei;vHJ6Y5Cg+tIp{YcH9?bUAr)xnOPcbEK>^mQFFj?xT1lxdGWO+QU`i55 zD{w-{2QkEN6P`0dMItQx#tQwNX*YP}W4^w|(GwVI93F1*{#;lWZ{JOo;OBl0ukhFZ zE%fYilm2<*qcuE@2xj=G!mC;Z-{SS4#B<`=v??v>5)hSaYVbK@)vFj3fb6Ggwc<(D z@+-33>c4}wCOL6uVetb)@SDgO*`~iKUpvTYf7B=Etef;fk;E@q3UC@I2&OrvV-X87 zW3C@}Fi)Q4My`K@qALr&`@Ug>ra4Ve&_FCzWBg9`%#A7&P#z_1BuEGCzl40lAJZ3L zeJ#ApGP$oJ=cQO^M77dQxozNg=|ZfNw1r)h72ZkR3PyTm+N>{MWHIHQjI23t3J?@p zkhf~YMyy@u`+ctWn=LYkq2)j5zDU{!jPRC%8u^stRK?k(+8*~A z@s^3e1@IsNO!!ugOMQxC84At9+9ISVK1M(>^-K|-$DgaJC zb&|3S$5AXS-f7iBNr}hd`=Om0rP^Hq?kHdo_wV@tUTw?4jM#ULG%y)d!u2%EEY6Yb zO>OFuXx5A5y1(65BEE(@LND}XgY!e{XwEgTRufHXmY$%xqefl;Sa8p_*TQ;+$0>aur=ydO#0IW{G4>qDU( zNciYAy|WcORy*F8{o#AeTX7i*pRrZ8dk4cWB)Ix`q0BS8%+{Y~eUym&J;66t`k$iV zE4P_n#Rk5zvgNF1C=y&220WRO^3AGa!q9(JFwmfca#xl+^)H`LR?`eq*Il)D;?^-HLB5I78;dT14Xyxo)d>&lzmWYHGvJ>Z}r1(Xm?v+%ZA$6$DXoyCy zu;RiNuDWh?g4+#^l~b=JpS+?(;&jJ#2Dw)#J@tf3EEzhT{N0z35lUOc_4xz3?Ctp) zfdeOp%R0M)XXR%Ju9}cvKQ*PA0`Xi1X3PLcrvxA?&ww;sBg1ift-+#jLY4R7PA}%+ zTE0X0Xidf%CPN+f+bnIN7s<4C>p#yVqe?SG;^Ru){9~DCzim<(JC}H+x1xG1Xb9s=@geGMG>X zA#5w6)4FIR)$m`lh)lbmiPZt-pM2Y7!dMD26)~+J&1400*KVNxblvMaCSz`-am-;E z@pS#gEX~z!*9f9<1L~V3@Z-qzqbI_)^nlU!$X@piu+{w$S`ttcDsP$vH zlXjlEhc~2rB|8jircK`>Y}VEfokR#PhqjnZS0yH|FPfS}%q(JsT)f&1;Ot561LH0N zO&9`Fbo1kSQc~nhEYW{~%UC?$qUfHmu?BNd{?epvx*t7CNjq&fI{=kH^W1VGskU?o zrgb7naUAR%E1V~C*Yo^M{Ct!S!3{|Jy}02m?S{_161CHu(*9Fc*D$OFede6cDn(;X zF&`Us0VYVvSkJtQegy4_co;ldl+%Y)AyfFLxH^6l&iI5m6q@O+nC{gfI9MyO_uUo$?gwdJwfj}AB&E)Hx3Q{&8XdhVEMK2F5?WxVhu&gQk0iEH-0&4pc>y-a@Q|m zK`w^fAe5JDmbNIZq?M8m&USpRsgM4@xnip0yR`hi<422C=}yNo*2B#0@0n6Me9qXg zy7coZJ6f+_n*03ax%w6#j=DFur*}3z?U58G%SW{q_PopqAzQmEsmWo#PD6Dh^3z0K zsP8S^)8#7t`4Ts5EyUaJJ4ztBhQhe{(2q^yMIxwW-JYy6t5d~q1uppTm|9ymSKl4? z`N7xf8@-r}+-`AlL0@r|dgx2rGUu%pA(fY+(VW{t z0C&7$HXXi$?gFs?h@oU$VKVO6zY5ocaWPexqX7EdNS+jfl}oY>+*JSstaD9XE7(|Y zh!;eV7CHlNzwq&Y)6#YP3EgBhqy-=$Z;?pzuaD&&i?}fyjP!^g`J3Fninp@wLe;fg z;>+3tqz8zs(FLE(o7vepb(7X73e2AyvU4 z4(XEidUU6oS>-m5>BZW_52>Wh2BnKiRUm-W^f>`qjM)z`ay3L^$7XW?=RNt4FcmwA z7dq69+wa^qi4(KLLiPgNUDKc^kgWJUGm*s$5O0>{GVdeP&8|W;eV5t|1jx9fQ8advK!E$>uM{Uos z`n|gwKY~m=B-nm}HZ(T-WdfRH8s2 zB9`m~pf6ic`>Hz{JP?v&2F5cwenX8cD!xoyr>&6$JwVaakRoeJ2^Ci!P-3sXegoE*Dd$f}ZrrVLs)*P~e?qo~ z0U8*K4d*`Ck(q_O)(=OJ?QPQ;?*;zMw41Lgxd0Qb=&e;W51%)^31P0KqEAwBu_pLdA<(Jc$&ARI;xU-r`C>2>J^uX`w5j%oWG zQeR^G4F$)xz2U175JxhD_0k0WJAfWX-$I;qr8i=KGBD%WIXOK*d0bqa)pK)Y7vSnX zI81wZL}$rZb|2~vvQ*UFWw%)vfZ!l|q&>qx|Nm!O_3FX!M-{xJBa4b2zPFe{z@PL> z@Ef^*YJG~Zwx*04IXEIVt_n14zS2i)TS=JtvGzL^^OzmF<;z#iE?uK;uVJcA!|T64 z7}C#4Q(v1vbL{_^2F3%RebWvhtMclXguBWVBc}Ih^`HvQ$&cOAmO6P%A9NJ*Ofo0& zkf#7s#qIo4YK77lExilyjKog*+T9ES_1VG(yfE<@oN*R~cR1ux5t9~ws42Zko#ymU+2EE8%q8yD8YpKoSo^b< zY22pI+o*QtSCVYIwR;5PuOX0xb{)U(vSN}(*P=jwxSi3R01q}DB<6uAH0!N=!{aSX z?$?>GLYZK?wrTy0;>CLkx^0RDjF4^g=b*7oX%haWk*09>mwk~jw>1C@2ch8{Yhu|? zw6x*`Rd{+_Vk@X)Jr(&RUXu$FyW)r!p!;kV7GmDo8^7;v5dIVf|OJwNel0$IKa^n%C)nmwl#S}SS+a0jq% o_{&NFyuJDE4ZJ)vXjO+- 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 discoveryServiceReg; private @Nullable MercedesMeMetadataAdjuster mdAdjuster; @@ -76,10 +74,8 @@ public class MercedesMeHandlerFactory extends BaseThingHandlerFactory { final @Reference LocaleProvider lp, final @Reference LocationProvider locationP, final @Reference TimeZoneProvider tzp, final @Reference MercedesMeCommandOptionProvider cop, final @Reference MercedesMeStateOptionProvider sop, final @Reference UnitProvider up, - final @Reference MetadataRegistry mdr, final @Reference ItemChannelLinkRegistry iclr, - final @Reference NetworkAddressService nas) { + final @Reference MetadataRegistry mdr, final @Reference ItemChannelLinkRegistry iclr) { this.storageService = storageService; - networkService = nas; localeProvider = lp; locationProvider = locationP; mmcop = cop; @@ -112,8 +108,7 @@ public class MercedesMeHandlerFactory extends BaseThingHandlerFactory { discoveryServiceReg = bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, null); } - return new AccountHandler((Bridge) thing, discoveryService, httpClient, localeProvider, storageService, - networkService); + return new AccountHandler((Bridge) thing, discoveryService, httpClient, localeProvider, storageService); } else if (THING_TYPE_BEV.equals(thingTypeUID) || THING_TYPE_COMB.equals(thingTypeUID) || THING_TYPE_HYBRID.equals(thingTypeUID)) { return new VehicleHandler(thing, locationProvider, mmcop, mmsop); diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/config/AccountConfiguration.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/config/AccountConfiguration.java index f6aee49314a..71f5b65a8dc 100644 --- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/config/AccountConfiguration.java +++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/config/AccountConfiguration.java @@ -26,9 +26,7 @@ public class AccountConfiguration { public String email = NOT_SET; public String region = NOT_SET; + public String refreshToken = "takeover previous token"; public String pin = NOT_SET; public int refreshInterval = 15; - - public String callbackIP = NOT_SET; - public int callbackPort = -1; } diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/dto/TokenResponse.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/dto/TokenResponse.java index 385c20dde7e..32bc9a8fa2b 100644 --- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/dto/TokenResponse.java +++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/dto/TokenResponse.java @@ -33,6 +33,6 @@ public class TokenResponse { @SerializedName("token_type") public String tokenType = Constants.NOT_SET; @SerializedName("expires_in") - public int expiresIn; - public String createdOn = Instant.now().toString(); + public int expiresIn = 0; + public String createdOn = Instant.MIN.toString(); } diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/AccountHandler.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/AccountHandler.java index 8e7e0f12312..51f7df0f85a 100644 --- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/AccountHandler.java +++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/AccountHandler.java @@ -38,15 +38,12 @@ import org.json.JSONObject; import org.openhab.binding.mercedesme.internal.Constants; import org.openhab.binding.mercedesme.internal.config.AccountConfiguration; import org.openhab.binding.mercedesme.internal.discovery.MercedesMeDiscoveryService; -import org.openhab.binding.mercedesme.internal.server.AuthServer; import org.openhab.binding.mercedesme.internal.server.AuthService; import org.openhab.binding.mercedesme.internal.server.MBWebsocket; import org.openhab.binding.mercedesme.internal.utils.Utils; import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener; import org.openhab.core.auth.client.oauth2.AccessTokenResponse; -import org.openhab.core.config.core.Configuration; import org.openhab.core.i18n.LocaleProvider; -import org.openhab.core.net.NetworkAddressService; import org.openhab.core.storage.Storage; import org.openhab.core.storage.StorageService; import org.openhab.core.thing.Bridge; @@ -80,7 +77,6 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr private static final String COMMAND_APPENDIX = "-commands"; private final Logger logger = LoggerFactory.getLogger(AccountHandler.class); - private final NetworkAddressService networkService; private final MercedesMeDiscoveryService discoveryService; private final HttpClient httpClient; private final LocaleProvider localeProvider; @@ -89,8 +85,6 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr private final Map vepUpdateMap = new HashMap<>(); private final Map> capabilitiesMap = new HashMap<>(); - private Optional server = Optional.empty(); - private Optional authService = Optional.empty(); private Optional> refreshScheduler = Optional.empty(); private List eventQueue = new ArrayList<>(); private boolean updateRunning = false; @@ -99,16 +93,16 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr private String commandCapabilitiesEndpoint = "/v1/vehicle/%s/capabilities/commands"; private String poiEndpoint = "/v1/vehicle/%s/route"; + Optional authService = Optional.empty(); final MBWebsocket ws; - Optional config = Optional.empty(); + AccountConfiguration config = new AccountConfiguration(); @Nullable ClientMessage message; public AccountHandler(Bridge bridge, MercedesMeDiscoveryService mmds, HttpClient hc, LocaleProvider lp, - StorageService store, NetworkAddressService nas) { + StorageService store) { super(bridge); discoveryService = mmds; - networkService = nas; ws = new MBWebsocket(this); httpClient = hc; localeProvider = lp; @@ -122,83 +116,39 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr @Override public void initialize() { updateStatus(ThingStatus.UNKNOWN); - config = Optional.of(getConfigAs(AccountConfiguration.class)); - autodetectCallback(); + config = getConfigAs(AccountConfiguration.class); String configValidReason = configValid(); if (!configValidReason.isEmpty()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configValidReason); } else { - String callbackUrl = Utils.getCallbackAddress(config.get().callbackIP, config.get().callbackPort); - thing.setProperty("callbackUrl", callbackUrl); - server = Optional.of(new AuthServer(httpClient, config.get(), callbackUrl)); - authService = Optional - .of(new AuthService(this, httpClient, config.get(), localeProvider.getLocale(), storage)); - if (!server.get().start()) { - String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId() - + Constants.STATUS_SERVER_RESTART; - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, - textKey + " [\"" + thing.getProperties().get("callbackUrl") + "\"]"); - } else { - refreshScheduler = Optional.of(scheduler.scheduleWithFixedDelay(this::refresh, 0, - config.get().refreshInterval, TimeUnit.MINUTES)); - } + authService = Optional.of(new AuthService(this, httpClient, config, localeProvider.getLocale(), storage, + config.refreshToken)); + refreshScheduler = Optional + .of(scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refreshInterval, TimeUnit.MINUTES)); } } public void refresh() { - if (server.isPresent()) { - if (!Constants.NOT_SET.equals(authService.get().getToken())) { - ws.run(); - } else { - // all failed - start manual authorization - String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId() - + Constants.STATUS_AUTH_NEEDED; - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - textKey + " [\"" + thing.getProperties().get("callbackUrl") + "\"]"); - } + if (!Constants.NOT_SET.equals(authService.get().getToken())) { + ws.run(); } else { - // server not running - fix first + // all failed - start manual authorization String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId() - + Constants.STATUS_SERVER_RESTART; - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, textKey); + + Constants.STATUS_AUTH_NEEDED; + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, textKey); } } - private void autodetectCallback() { - // if Callback IP and Callback Port are not set => autodetect these values - config = Optional.of(getConfigAs(AccountConfiguration.class)); - Configuration updateConfig = super.editConfiguration(); - if (!updateConfig.containsKey("callbackPort")) { - updateConfig.put("callbackPort", Utils.getFreePort()); - } else { - Utils.addPort(config.get().callbackPort); - } - if (!updateConfig.containsKey("callbackIP")) { - String ip = networkService.getPrimaryIpv4HostAddress(); - if (ip != null) { - updateConfig.put("callbackIP", ip); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, - "@text/mercedesme.account.status.ip-autodetect-failure"); - } - } - super.updateConfiguration(updateConfig); - // get new config after update - config = Optional.of(getConfigAs(AccountConfiguration.class)); - } - private String configValid() { - config = Optional.of(getConfigAs(AccountConfiguration.class)); + config = getConfigAs(AccountConfiguration.class); String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId(); - if (Constants.NOT_SET.equals(config.get().callbackIP)) { - return textKey + Constants.STATUS_IP_MISSING; - } else if (config.get().callbackPort == -1) { - return textKey + Constants.STATUS_PORT_MISSING; - } else if (Constants.NOT_SET.equals(config.get().email)) { + if (Constants.NOT_SET.equals(config.refreshToken)) { + return textKey + Constants.STATUS_REFRESH_TOKEN_MISSING; + } else if (Constants.NOT_SET.equals(config.email)) { return textKey + Constants.STATUS_EMAIL_MISSING; - } else if (Constants.NOT_SET.equals(config.get().region)) { + } else if (Constants.NOT_SET.equals(config.region)) { return textKey + Constants.STATUS_REGION_MISSING; - } else if (config.get().refreshInterval <= 01) { + } else if (config.refreshInterval < 5) { return textKey + Constants.STATUS_REFRESH_INVALID; } else { return Constants.EMPTY; @@ -207,13 +157,6 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr @Override public void dispose() { - if (server.isPresent()) { - AuthServer authServer = server.get(); - authServer.stop(); - authServer.dispose(); - server = Optional.empty(); - Utils.removePort(config.get().callbackPort); - } refreshScheduler.ifPresent(schedule -> { if (!schedule.isCancelled()) { schedule.cancel(true); @@ -223,6 +166,13 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr eventQueue.clear(); } + @Override + public void handleRemoval() { + storage.remove(config.email); + authService = Optional.empty(); + super.handleRemoval(); + } + /** * https://next.openhab.org/javadoc/latest/org/openhab/core/auth/client/oauth2/package-summary.html */ @@ -230,27 +180,16 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr public void onAccessTokenResponse(AccessTokenResponse tokenResponse) { if (!Constants.NOT_SET.equals(tokenResponse.getAccessToken())) { scheduler.schedule(this::refresh, 2, TimeUnit.SECONDS); - } else if (server.isEmpty()) { - // server not running - fix first - String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId() - + Constants.STATUS_SERVER_RESTART; - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, textKey); } else { // all failed - start manual authorization String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId() + Constants.STATUS_AUTH_NEEDED; - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - textKey + " [\"" + thing.getProperties().get("callbackUrl") + "\"]"); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, textKey); } } - @Override - public String toString() { - return Integer.toString(config.get().callbackPort); - } - public String getWSUri() { - return Utils.getWebsocketServer(config.get().region); + return Utils.getWebsocketServer(config.region); } public ClientUpgradeRequest getClientUpgradeRequest() { @@ -260,12 +199,12 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr request.setHeader("X-TrackingId", UUID.randomUUID().toString()); request.setHeader("Ris-Os-Name", Constants.RIS_OS_NAME); request.setHeader("Ris-Os-Version", Constants.RIS_OS_VERSION); - request.setHeader("Ris-Sdk-Version", Utils.getRisSDKVersion(config.get().region)); + request.setHeader("Ris-Sdk-Version", Utils.getRisSDKVersion(config.region)); request.setHeader("X-Locale", localeProvider.getLocale().getLanguage() + "-" + localeProvider.getLocale().getCountry()); // de-DE - request.setHeader("User-Agent", Utils.getApplication(config.get().region)); - request.setHeader("X-Applicationname", Utils.getUserAgent(config.get().region)); - request.setHeader("Ris-Application-Version", Utils.getRisApplicationVersion(config.get().region)); + request.setHeader("User-Agent", Utils.getApplication(config.region)); + request.setHeader("X-Applicationname", Utils.getUserAgent(config.region)); + request.setHeader("Ris-Application-Version", Utils.getRisApplicationVersion(config.region)); return request; } @@ -452,8 +391,7 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr Map featureMap = new HashMap<>(); try { // add vehicle capabilities - String capabilitiesUrl = Utils.getRestAPIServer(config.get().region) - + String.format(capabilitiesEndpoint, vin); + String capabilitiesUrl = Utils.getRestAPIServer(config.region) + String.format(capabilitiesEndpoint, vin); Request capabilitiesRequest = httpClient.newRequest(capabilitiesUrl); authService.get().addBasicHeaders(capabilitiesRequest); capabilitiesRequest.header("X-SessionId", UUID.randomUUID().toString()); @@ -489,7 +427,7 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr } // add command capabilities - String commandCapabilitiesUrl = Utils.getRestAPIServer(config.get().region) + String commandCapabilitiesUrl = Utils.getRestAPIServer(config.region) + String.format(commandCapabilitiesEndpoint, vin); Request commandCapabilitiesRequest = httpClient.newRequest(commandCapabilitiesUrl); authService.get().addBasicHeaders(commandCapabilitiesRequest); @@ -557,7 +495,7 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr */ public void sendPoi(String vin, JSONObject poi) { - String poiUrl = Utils.getRestAPIServer(config.get().region) + String.format(poiEndpoint, vin); + String poiUrl = Utils.getRestAPIServer(config.region) + String.format(poiEndpoint, vin); Request poiRequest = httpClient.POST(poiUrl); authService.get().addBasicHeaders(poiRequest); poiRequest.header("X-SessionId", UUID.randomUUID().toString()); diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandler.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandler.java index 586d2600f4c..12e3a533987 100644 --- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandler.java +++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandler.java @@ -216,7 +216,7 @@ public class VehicleHandler extends BaseThingHandler { var crBuilder = CommandRequest.newBuilder().setVin(config.get().vin).setRequestId(UUID.randomUUID().toString()); String group = channelUID.getGroupId(); String channel = channelUID.getIdWithoutGroup(); - String pin = accountHandler.get().config.get().pin; + String pin = accountHandler.get().config.pin; if (group == null) { logger.trace("No command {} found for {}", command, channel); return; diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServer.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServer.java deleted file mode 100644 index 5cac0ee07d9..00000000000 --- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServer.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.mercedesme.internal.server; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.ServletHandler; -import org.openhab.binding.mercedesme.internal.Constants; -import org.openhab.binding.mercedesme.internal.config.AccountConfiguration; -import org.openhab.core.auth.client.oauth2.AccessTokenResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * {@link AuthServer} provides HTTP Server to show servlet content of the authentication process - * - * @author Bernd Weymann - Initial contribution - */ -@NonNullByDefault -public class AuthServer { - private static final Logger LOGGER = LoggerFactory.getLogger(AuthServer.class); - private static final Map SERVER_MAP = new HashMap<>(); - private static final AccessTokenResponse INVALID_ACCESS_TOKEN = new AccessTokenResponse(); - - private final HttpClient httpClient; - - private Optional server = Optional.empty(); - private AccountConfiguration config; - public String callbackUrl; - - public AuthServer(HttpClient hc, AccountConfiguration config, String callbackUrl) { - httpClient = hc; - SERVER_MAP.put(Integer.valueOf(config.callbackPort), this); - this.config = config; - this.callbackUrl = callbackUrl; - INVALID_ACCESS_TOKEN.setAccessToken(Constants.EMPTY); - } - - public void dispose() { - SERVER_MAP.remove(Integer.valueOf(config.callbackPort)); - } - - public boolean start() { - // avoid real server start for unit tests - if (server.isPresent() || Constants.JUNIT_SERVER_ADDR.equals(callbackUrl)) { - return true; - } - server = Optional.of(new Server()); - ServerConnector connector = new ServerConnector(server.get()); - connector.setPort(config.callbackPort); - server.get().setConnectors(new Connector[] { connector }); - ServletHandler servletHandler = new ServletHandler(); - server.get().setHandler(servletHandler); - servletHandler.addServletWithMapping(AuthServlet.class, Constants.CALLBACK_ENDPOINT); - try { - server.get().start(); - return true; - } catch (Exception e) { - LOGGER.trace("Cannot start Callback Server for port {}, Error {}", config.callbackPort, e.getMessage()); - server = Optional.empty(); - return false; - } - } - - public void stop() { - try { - if (server.isPresent()) { - server.get().stop(); - server = Optional.empty(); - } - } catch (Exception e) { - LOGGER.trace("Cannot start Callback Server for port {}, Error {}", config.callbackPort, e.getMessage()); - } - } - - @Nullable - public static AuthServer getServer(int port) { - return SERVER_MAP.get(port); - } - - public HttpClient getHttpClient() { - return httpClient; - } - - public String getRegion() { - return config.region; - } -} diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthService.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthService.java index c660545c958..585f9a1cc35 100644 --- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthService.java +++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthService.java @@ -16,9 +16,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.HashMap; import java.util.Locale; -import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -33,7 +31,6 @@ import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.http.HttpHeader; import org.openhab.binding.mercedesme.internal.Constants; import org.openhab.binding.mercedesme.internal.config.AccountConfiguration; -import org.openhab.binding.mercedesme.internal.dto.PINRequest; import org.openhab.binding.mercedesme.internal.dto.TokenResponse; import org.openhab.binding.mercedesme.internal.utils.Utils; import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener; @@ -42,6 +39,8 @@ import org.openhab.core.storage.Storage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.JsonSyntaxException; + /** * {@link AuthService} helpers for token management * @@ -49,23 +48,19 @@ import org.slf4j.LoggerFactory; */ @NonNullByDefault public class AuthService { - public static final AccessTokenResponse INVALID_TOKEN = new AccessTokenResponse(); private static final int EXPIRATION_BUFFER = 5; - private static final Map AUTH_MAP = new HashMap<>(); private final Logger logger = LoggerFactory.getLogger(AuthService.class); - AccessTokenRefreshListener listener; + private AccessTokenRefreshListener listener; + private AccountConfiguration config; + private AccessTokenResponse token = Utils.INVALID_TOKEN; + private Storage storage; private HttpClient httpClient; private String identifier; - private AccountConfiguration config; private Locale locale; - private Storage storage; - private AccessTokenResponse token; public AuthService(AccessTokenRefreshListener atrl, HttpClient hc, AccountConfiguration ac, Locale l, - Storage store) { - INVALID_TOKEN.setAccessToken(Constants.NOT_SET); - INVALID_TOKEN.setRefreshToken(Constants.NOT_SET); + Storage store, String refreshToken) { listener = atrl; httpClient = hc; config = ac; @@ -73,129 +68,53 @@ public class AuthService { locale = l; storage = store; - // restore token - String storedObject = storage.get(identifier); - if (storedObject == null) { - token = INVALID_TOKEN; - listener.onAccessTokenResponse(token); - } else { - token = Utils.fromString(storedObject); - if (token.isExpired(Instant.now(), EXPIRATION_BUFFER)) { - if (!Constants.NOT_SET.equals(token.getRefreshToken())) { - refreshToken(); - listener.onAccessTokenResponse(token); - } else { - token = INVALID_TOKEN; - listener.onAccessTokenResponse(token); + // restore token from persistence if available + String storedToken = storage.get(identifier); + if (storedToken != null) { + // returns INVALID_TOKEN in case of an error + logger.trace("MB-Auth {} Restore token from persistence", prefix()); + try { + logger.trace("MB-Auth {} storedToken {}", prefix(), storedToken); + TokenResponse tokenResponseJson = Utils.GSON.fromJson(storedToken, TokenResponse.class); + token = decodeToken(tokenResponseJson); + if (!tokenIsValid()) { + token = Utils.INVALID_TOKEN; + storage.remove(identifier); + logger.trace("MB-Auth {} invalid storedToken {}", prefix(), storedToken); } - } else { - listener.onAccessTokenResponse(token); + } catch (JsonSyntaxException jse) { + // fallback of non human readable base64 token persistence + logger.debug("MB-Auth {} Fallback token decoding", prefix()); + token = Utils.fromString(storedToken); } + } else { + // initialize token with refresh token from configuration and expiration 0 + // this will trigger an immediately refresh of the token + logger.trace("MB-Auth {} Create token from config", prefix()); + token = new AccessTokenResponse(); + token.setAccessToken(refreshToken); + token.setRefreshToken(refreshToken); + token.setExpiresIn(0); } - AUTH_MAP.put(config.callbackPort, this); } - @Nullable - public static AuthService getAuthService(Integer key) { - return AUTH_MAP.get(key); + public synchronized String getToken() { + if (token.isExpired(Instant.now(), EXPIRATION_BUFFER)) { + if (tokenIsValid()) { + refreshToken(); + } + } + return token.getAccessToken(); } - /** - * - * @return guid from request to create token in next step - */ - public String requestPin() { - String configUrl = Utils.getAuthConfigURL(config.region); - String sessionId = UUID.randomUUID().toString(); - Request configRequest = httpClient.newRequest(configUrl); - addBasicHeaders(configRequest); - configRequest.header("X-Trackingid", UUID.randomUUID().toString()); - configRequest.header("X-Sessionid", sessionId); - try { - ContentResponse cr = configRequest.timeout(Constants.REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS).send(); - if (cr.getStatus() == 200) { - logger.trace("{} Config Request PIN fine {} {}", prefix(), cr.getStatus(), cr.getContentAsString()); - } else { - logger.trace("{} Failed to request config for pin {} {}", prefix(), cr.getStatus(), - cr.getContentAsString()); - return Constants.NOT_SET; - } - } catch (InterruptedException | TimeoutException | ExecutionException e) { - logger.trace("{} Failed to request config for pin {}", prefix(), e.getMessage()); - return Constants.NOT_SET; - } - - String url = Utils.getAuthURL(config.region); - Request req = httpClient.POST(url); - addBasicHeaders(req); - req.header("X-Trackingid", UUID.randomUUID().toString()); - req.header("X-Sessionid", sessionId); - - PINRequest pr = new PINRequest(config.email, locale.getCountry()); - req.header(HttpHeader.CONTENT_TYPE, "application/json"); - logger.trace("{} payload {}", url, Utils.GSON.toJson(pr)); - req.content(new StringContentProvider(Utils.GSON.toJson(pr), "utf-8")); - - try { - ContentResponse cr = req.timeout(Constants.REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS).send(); - if (cr.getStatus() == 200) { - logger.trace("{} Request PIN fine {} {}", prefix(), cr.getStatus(), cr.getContentAsString()); - return pr.nonce; - } else { - logger.trace("{} Failed to request pin {} {}", prefix(), cr.getStatus(), cr.getContentAsString()); - } - } catch (InterruptedException | TimeoutException | ExecutionException e) { - logger.trace("{} Failed to request pin {}", prefix(), e.getMessage()); - } - return Constants.NOT_SET; - } - - public boolean requestToken(String password) { - try { - // Request + headers - String url = Utils.getTokenUrl(config.region); - Request req = httpClient.POST(url); - addBasicHeaders(req); - req.header("Stage", "prod"); - req.header("X-Device-Id", UUID.randomUUID().toString()); - req.header("X-Request-Id", UUID.randomUUID().toString()); - - // Content URL form - String clientId = "client_id=" - + URLEncoder.encode(Utils.getLoginAppId(config.region), StandardCharsets.UTF_8.toString()); - String grantAttribute = "grant_type=password"; - String userAttribute = "username=" + URLEncoder.encode(config.email, StandardCharsets.UTF_8.toString()); - String passwordAttribute = "password=" + URLEncoder.encode(password, StandardCharsets.UTF_8.toString()); - String scopeAttribute = "scope=" + URLEncoder.encode(Constants.SCOPE, StandardCharsets.UTF_8.toString()); - String content = clientId + "&" + grantAttribute + "&" + userAttribute + "&" + passwordAttribute + "&" - + scopeAttribute; - req.header(HttpHeader.CONTENT_TYPE, "application/x-www-form-urlencoded"); - req.content(new StringContentProvider(content)); - - // Send - ContentResponse cr = req.timeout(Constants.REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS).send(); - if (cr.getStatus() == 200) { - String responseString = cr.getContentAsString(); - saveTokenResponse(responseString); - listener.onAccessTokenResponse(token); - return true; - } else { - logger.trace("{} Failed to get token {} {}", prefix(), cr.getStatus(), cr.getContentAsString()); - } - } catch (InterruptedException | TimeoutException | ExecutionException | UnsupportedEncodingException e) { - logger.trace("{} Failed to get token {}", prefix(), e.getMessage()); - } - return false; - } - - public void refreshToken() { + private void refreshToken() { + logger.trace("MB-Auth {} refreshToken", prefix()); try { String url = Utils.getTokenUrl(config.region); Request req = httpClient.POST(url); req.header("X-Device-Id", UUID.randomUUID().toString()); req.header("X-Request-Id", UUID.randomUUID().toString()); - // Content URL form String grantAttribute = "grant_type=refresh_token"; String refreshTokenAttribute = "refresh_token=" + URLEncoder.encode(token.getRefreshToken(), StandardCharsets.UTF_8.toString()); @@ -203,35 +122,79 @@ public class AuthService { req.header(HttpHeader.CONTENT_TYPE, "application/x-www-form-urlencoded"); req.content(new StringContentProvider(content)); - // Send ContentResponse cr = req.timeout(Constants.REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS).send(); - if (cr.getStatus() == 200) { - saveTokenResponse(cr.getContentAsString()); - listener.onAccessTokenResponse(token); + int tokenResponseStatus = cr.getStatus(); + String tokenResponse = cr.getContentAsString(); + if (tokenResponseStatus == 200) { + TokenResponse tokenResponseJson = Utils.GSON.fromJson(tokenResponse, TokenResponse.class); + if (tokenResponseJson != null) { + // response doesn't contain creation date time so set it manually + tokenResponseJson.createdOn = Instant.now().toString(); + // a new refresh token is delivered optional + // if not set in response take old one + if (Constants.NOT_SET.equals(tokenResponseJson.refreshToken)) { + tokenResponseJson.refreshToken = token.getRefreshToken(); + } + token = decodeToken(tokenResponseJson); + if (tokenIsValid()) { + String tokenStore = Utils.GSON.toJson(tokenResponseJson); + logger.debug("MB-Auth {} refreshToken result {}", prefix(), token.toString()); + storage.put(identifier, tokenStore); + } else { + token = Utils.INVALID_TOKEN; + storage.remove(identifier); + logger.warn("MB-Auth {} Refresh token delivered invalid result {} {}", prefix(), + tokenResponseStatus, tokenResponse); + } + } else { + logger.debug("MB-Auth {} token refersh delivered not parsable result {}", prefix(), tokenResponse); + token = Utils.INVALID_TOKEN; + } } else { - logger.trace("{} Failed to refresh token {} {}", prefix(), cr.getStatus(), cr.getContentAsString()); + token = Utils.INVALID_TOKEN; + /** + * 1) remove token from storage + * 2) listener will be informed about INVALID_TOKEN and bridge will go OFFLINE + * 3) user needs to update refreshToken configuration parameter + */ + storage.remove(identifier); + logger.warn("MB-Auth {} Failed to refresh token {} {}", prefix(), tokenResponseStatus, tokenResponse); } - } catch (InterruptedException | TimeoutException | ExecutionException | UnsupportedEncodingException e) { - logger.trace("{} Failed to refresh token {}", prefix(), e.getMessage()); + listener.onAccessTokenResponse(token); + } catch (InterruptedException | TimeoutException | ExecutionException | UnsupportedEncodingException + | JsonSyntaxException e) { + logger.info("{} Failed to refresh token {}", prefix(), e.getMessage()); } } - public String getToken() { - if (token.isExpired(Instant.now(), EXPIRATION_BUFFER)) { - if (!Constants.NOT_SET.equals(token.getRefreshToken())) { - refreshToken(); - // token shall be updated now - retry expired check - if (token.isExpired(Instant.now(), EXPIRATION_BUFFER)) { - token = INVALID_TOKEN; - listener.onAccessTokenResponse(token); - return Constants.NOT_SET; - } + private AccessTokenResponse decodeToken(@Nullable TokenResponse tokenJson) { + if (tokenJson != null) { + AccessTokenResponse atr = new AccessTokenResponse(); + atr.setCreatedOn(Instant.parse(tokenJson.createdOn)); + atr.setExpiresIn(tokenJson.expiresIn); + atr.setAccessToken(tokenJson.accessToken); + if (!Constants.NOT_SET.equals(tokenJson.refreshToken)) { + atr.setRefreshToken(tokenJson.refreshToken); } else { - token = INVALID_TOKEN; - logger.trace("{} Refresh token empty", prefix()); + // Preserve refresh token if available + if (!Constants.NOT_SET.equals(token.getRefreshToken())) { + atr.setRefreshToken(token.getRefreshToken()); + } else { + logger.debug("MB-Auth {} Neither new nor old refresh token available", prefix()); + return Utils.INVALID_TOKEN; + } } + atr.setTokenType("Bearer"); + atr.setScope(Constants.SCOPE); + return atr; + } else { + logger.debug("MB-Auth {} Neither Token Response is null", prefix()); } - return token.getAccessToken(); + return Utils.INVALID_TOKEN; + } + + private boolean tokenIsValid() { + return !Constants.NOT_SET.equals(token.getAccessToken()) && !Constants.NOT_SET.equals(token.getRefreshToken()); } public void addBasicHeaders(Request req) { @@ -244,30 +207,6 @@ public class AuthService { req.header("Ris-Application-Version", Utils.getRisApplicationVersion(config.region)); } - private void saveTokenResponse(String response) { - TokenResponse tr = Utils.GSON.fromJson(response, TokenResponse.class); - AccessTokenResponse atr = new AccessTokenResponse(); - if (tr != null) { - atr.setAccessToken(tr.accessToken); - atr.setCreatedOn(Instant.now()); - atr.setExpiresIn(tr.expiresIn); - // Preserve refresh token if available - if (Constants.NOT_SET.equals(tr.refreshToken) && !Constants.NOT_SET.equals(token.getRefreshToken())) { - atr.setRefreshToken(token.getRefreshToken()); - } else if (!Constants.NOT_SET.equals(tr.refreshToken)) { - atr.setRefreshToken(tr.refreshToken); - } else { - logger.trace("{} Neither new nor old refresh token available", prefix()); - } - atr.setTokenType("Bearer"); - atr.setScope(Constants.SCOPE); - storage.put(identifier, Utils.toString(atr)); - token = atr; - } else { - logger.trace("{} Token Response is null", prefix()); - } - } - private String prefix() { return "[" + config.email + "] "; } diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServlet.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServlet.java deleted file mode 100644 index 95bfb4ec147..00000000000 --- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServlet.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.mercedesme.internal.server; - -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.mercedesme.internal.Constants; - -/** - * {@link AuthServlet} provides simple HTML pages for authorization workflow - * - * @author Bernd Weymann - Initial contribution - */ -@SuppressWarnings("serial") -@NonNullByDefault -public class AuthServlet extends HttpServlet { - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - AuthService myAuthService = AuthService.getAuthService(request.getLocalPort()); - String guid = request.getParameter(Constants.GUID); - String pin = request.getParameter(Constants.PIN); - if (guid == null && pin == null && myAuthService != null) { - // request PIN - String requestVal = myAuthService.requestPin(); - if (!Constants.NOT_SET.equals(requestVal)) { - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().println(""); - response.getWriter().println(""); - response.getWriter().println("

Step 1 - PIN Requested

"); - response.getWriter().println("
"); - response.getWriter().println("PIN was requested and should be present in your EMail Inbox
"); - response.getWriter() - .println("Check first if you received the PIN and then continue with the below Link
"); - response.getWriter().println("Click here to continue with Step 2"); - response.getWriter().println(""); - response.getWriter().println(""); - } else { - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().println(""); - response.getWriter().println(""); - response.getWriter().println("Something went wrong
"); - response.getWriter().println(""); - response.getWriter().println(""); - } - - } else if (guid != null && pin == null && myAuthService != null) { - // show insert PIN input field - - response.setContentType("text/html"); - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().println(""); - response.getWriter().println(""); - response.getWriter().println("

Step 2 - Enter PIN

"); - response.getWriter().println("
"); - response.getWriter().println("Enter PIN in second input field - leave guid as it is!
"); - response.getWriter().println("
"); - response.getWriter().println("
"); - response.getWriter().println(""); - response.getWriter().println(""); - response.getWriter().println("
"); - response.getWriter().println(""); - response.getWriter().println(""); - response.getWriter().println("
"); - response.getWriter().println(""); - response.getWriter().println("
"); - response.getWriter().println(""); - response.getWriter().println(""); - } else if (guid != null && pin != null && myAuthService != null) { - // call getToken and show result - boolean result = myAuthService.requestToken(guid + ":" + pin); - response.setContentType("text/html"); - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().println(""); - response.getWriter().println(""); - response.getWriter().println("

Step 3 - Save Token

"); - response.getWriter().println("
"); - if (result) { - response.getWriter().println("Success - everything done!
"); - } else { - response.getWriter().println("Failure - Please check logs for further analysis!
"); - } - response.getWriter().println(""); - response.getWriter().println(""); - } - } -} diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/MBWebsocket.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/MBWebsocket.java index 26c7ed05a93..04db1b4e034 100644 --- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/MBWebsocket.java +++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/MBWebsocket.java @@ -93,11 +93,11 @@ public class MBWebsocket { client.setStopTimeout(CONNECT_TIMEOUT_MS); ClientUpgradeRequest request = accountHandler.getClientUpgradeRequest(); String websocketURL = accountHandler.getWSUri(); - logger.trace("Websocket start {}", websocketURL); if (Constants.JUNIT_TOKEN.equals(request.getHeader("Authorization"))) { // avoid unit test requesting real websocket - simply return return; } + logger.trace("Websocket start {}", websocketURL); client.start(); client.connect(this, new URI(websocketURL), request); while (keepAlive || Instant.now().isBefore(runTill)) { @@ -177,7 +177,8 @@ public class MBWebsocket { } } else { if (!b) { - // after keep alive is finished add 5 minutes to cover e.g. door events after trip is finished + // after keep alive is finished add 5 minutes to cover e.g. door events after + // trip is finished runTill = Instant.now().plusMillis(KEEP_ALIVE_ADDON); logger.trace("Websocket - keep alive stop - run till {}", runTill.toString()); } @@ -199,8 +200,10 @@ public class MBWebsocket { * https://community.openhab.org/t/mercedes-me/136866/12 * Release Websocket thread as early as possible to avoid execeptions * - * 1. Websocket thread responsible for reading stream in bytes and enqueue for AccountHandler. - * 2. AccountHamdler thread responsible for encoding proto message. In case of update enqueue proto message + * 1. Websocket thread responsible for reading stream in bytes and enqueue for + * AccountHandler. + * 2. AccountHamdler thread responsible for encoding proto message. In case of + * update enqueue proto message * at VehicleHandöer * 3. VehicleHandler responsible to update channels */ @@ -225,6 +228,7 @@ public class MBWebsocket { @OnWebSocketError public void onError(Throwable t) { + logger.debug("Error during web socket connection - {}", t.getMessage()); accountHandler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/mercedesme.account.status.websocket-failure [\"" + t.getMessage() + "\"]"); } diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/utils/Utils.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/utils/Utils.java index 0954469607e..b64c5a76398 100644 --- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/utils/Utils.java +++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/utils/Utils.java @@ -13,10 +13,8 @@ package org.openhab.binding.mercedesme.internal.utils; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.time.Duration; import java.time.Instant; import java.time.ZoneId; @@ -37,7 +35,6 @@ import org.json.JSONArray; import org.json.JSONObject; import org.openhab.binding.mercedesme.internal.Constants; import org.openhab.binding.mercedesme.internal.MercedesMeHandlerFactory; -import org.openhab.binding.mercedesme.internal.server.AuthService; import org.openhab.core.auth.client.oauth2.AccessTokenResponse; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.TimeZoneProvider; @@ -78,14 +75,13 @@ public class Utils { private static final int R = 6371; // Radius of the earth private static int port = 8090; - private static TimeZoneProvider timeZoneProvider = new TimeZoneProvider() { + public static TimeZoneProvider timeZoneProvider = new TimeZoneProvider() { @Override public ZoneId getTimeZone() { return ZoneId.systemDefault(); } }; - private static LocaleProvider localeProvider = new LocaleProvider() { - + public static LocaleProvider localeProvider = new LocaleProvider() { @Override public Locale getLocale() { return Locale.getDefault(); @@ -94,10 +90,13 @@ public class Utils { public static final Gson GSON = new Gson(); public static final Map ZONE_HASHMAP = new HashMap<>(); public static final Map PROGRAM_HASHMAP = new HashMap<>(); + public static final AccessTokenResponse INVALID_TOKEN = new AccessTokenResponse(); public static void initialize(TimeZoneProvider tzp, LocaleProvider lp) { timeZoneProvider = tzp; localeProvider = lp; + INVALID_TOKEN.setAccessToken(Constants.NOT_SET); + INVALID_TOKEN.setRefreshToken(Constants.NOT_SET); } /** @@ -138,27 +137,6 @@ public class Utils { return port; } - /** - * Register port for an AccountHandler - */ - public static synchronized void addPort(int portNr) { - if (PORTS.contains(portNr) && portNr != 99999) { - LOGGER.warn("Port {} already occupied", portNr); - } - PORTS.add(portNr); - } - - /** - * Unregister port for an AccountHandler - */ - public static synchronized void removePort(int portNr) { - PORTS.remove(Integer.valueOf(portNr)); - } - - public static String getCallbackAddress(String callbackIP, int callbackPort) { - return "http://" + callbackIP + Constants.COLON + callbackPort + Constants.CALLBACK_ENDPOINT; - } - /** * Calculate REST API server address according to region * @@ -286,41 +264,6 @@ public class Utils { } } - /** - * Calculate authorization config URL as pre-configuration prior to authorization call - * - * @param region - configured region - * @return authorization config URL as String - */ - public static String getAuthConfigURL(String region) { - return getRestAPIServer(region) + "/v1/config"; - } - - /** - * Calculate login app id according to region - * - * @param region - configured region - * @return login app id as String - */ - public static String getLoginAppId(String region) { - switch (region) { - case Constants.REGION_CHINA: - return Constants.LOGIN_APP_ID_CN; - default: - return Constants.LOGIN_APP_ID; - } - } - - /** - * Calculate authorization URL for authorization call - * - * @param region - configured region - * @return authorization URL as String - */ - public static String getAuthURL(String region) { - return getRestAPIServer(region) + "/v1/login"; - } - /** * Calculate token URL for getting token * @@ -337,6 +280,7 @@ public class Utils { * @param token - Base64 String from storage * @return AccessTokenResponse decoded from String, invalid token otherwise */ + @Deprecated public static AccessTokenResponse fromString(String token) { try { byte[] data = Base64.getDecoder().decode(token); @@ -347,25 +291,7 @@ public class Utils { } catch (IOException | ClassNotFoundException e) { LOGGER.warn("Error converting string to token {}", e.getMessage()); } - return AuthService.INVALID_TOKEN; - } - - /** - * Encode AccessTokenResponse as Base64 String for storage - * - * @param token - AccessTokenResponse to convert - */ - public static String toString(AccessTokenResponse token) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos); - oos.writeObject(token); - oos.close(); - return Base64.getEncoder().encodeToString(baos.toByteArray()); - } catch (IOException e) { - LOGGER.warn("Error converting token to string {}", e.getMessage()); - } - return Constants.NOT_SET; + return INVALID_TOKEN; } /** diff --git a/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/config/bridge-config.xml b/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/config/bridge-config.xml index c73b7c5b011..d61b45b6097 100644 --- a/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/config/bridge-config.xml +++ b/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/config/bridge-config.xml @@ -19,6 +19,11 @@ + + + Refresh Token from MB Token Requester app + "takeover previous token" + PIN for commands @@ -29,15 +34,5 @@ Refresh Interval in Minutes 15 - - - IP address for openHAB callback URL - true - - - - Port Number for openHAB callback URL - true - diff --git a/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/i18n/mercedesme.properties b/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/i18n/mercedesme.properties index a18833ea11b..461ccb4a12a 100644 --- a/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/i18n/mercedesme.properties +++ b/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/i18n/mercedesme.properties @@ -19,16 +19,14 @@ thing-type.mercedesme.hybrid.description = Conventional Fuel Vehicle with suppor thing-type.config.mercedesme.bev.batteryCapacity.label = Battery Capacity thing-type.config.mercedesme.bev.batteryCapacity.description = Battery capacity in kWh of vehicle thing-type.config.mercedesme.bev.vin.label = Vehicle Identification Number -thing-type.config.mercedesme.bridge.callbackIP.label = Callback IP Address -thing-type.config.mercedesme.bridge.callbackIP.description = IP address for openHAB callback URL -thing-type.config.mercedesme.bridge.callbackPort.label = Callback Port Number -thing-type.config.mercedesme.bridge.callbackPort.description = Port Number for openHAB callback URL thing-type.config.mercedesme.bridge.email.label = MercedesMe EMail thing-type.config.mercedesme.bridge.email.description = EMail address for MercedesMe account thing-type.config.mercedesme.bridge.pin.label = PIN thing-type.config.mercedesme.bridge.pin.description = PIN for commands thing-type.config.mercedesme.bridge.refreshInterval.label = Refresh Interval thing-type.config.mercedesme.bridge.refreshInterval.description = Refresh Interval in Minutes +thing-type.config.mercedesme.bridge.refreshToken.label = Refresh Token +thing-type.config.mercedesme.bridge.refreshToken.description = Refresh Token from MB Token Requester app thing-type.config.mercedesme.bridge.region.label = Region thing-type.config.mercedesme.bridge.region.option.EU = Europe thing-type.config.mercedesme.bridge.region.option.NA = North America @@ -374,13 +372,10 @@ longitudeDescription = Longitude of the location # thing status types -mercedesme.account.status.authorization-needed = Manual Authorization needed at {0} +mercedesme.account.status.authorization-needed = Generate new refresh token +mercedesme.account.status.refresh-token-missing = Refresh token missing mercedesme.account.status.email-missing = EMail missing mercedesme.account.status.region-missing = Region missing mercedesme.account.status.refresh-invalid = Refresh Interval Invalid -mercedesme.account.status.ip-missing = Callback IP missing -mercedesme.account.status.port-missing = Callback Port missing -mercedesme.account.status.server-restart = Disable and enable Bridge to restart Authorization Server mercedesme.vehicle.status.bridge-missing = Bridge not set -mercedesme.account.status.ip-autodetect-failure = Callback IP cannot be detected mercedesme.account.status.websocket-failure = Websocket Exception: Reason: {0} diff --git a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/StatusTests.java b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/StatusTests.java index 7506d8f5c8a..37d4639ff06 100644 --- a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/StatusTests.java +++ b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/StatusTests.java @@ -13,12 +13,18 @@ package org.openhab.binding.mercedesme; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; -import java.time.Instant; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; import org.junit.jupiter.api.Test; import org.openhab.binding.mercedesme.internal.Constants; import org.openhab.binding.mercedesme.internal.handler.AccountHandlerMock; @@ -33,7 +39,7 @@ import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.internal.BridgeImpl; /** - * {@link StatusTests} sequencess for testing ThingStatus + * {@link StatusTests} sequences for testing ThingStatus * * @author Bernd Weymann - Initial contribution */ @@ -41,23 +47,41 @@ import org.openhab.core.thing.internal.BridgeImpl; class StatusTests { public static void tearDown(AccountHandlerMock ahm) { - // ahm.setCallback(null); - ahm.dispose(); try { Thread.sleep(250); } catch (InterruptedException e) { fail(); } + ahm.dispose(); + } + + public static HttpClient getHttpClient(int tokenResponseCode) { + Utils.initialize(Utils.timeZoneProvider, Utils.localeProvider); + HttpClient httpClient = mock(HttpClient.class); + try { + Request clientRequest = mock(Request.class); + when(httpClient.POST(anyString())).thenReturn(clientRequest); + when(clientRequest.header(anyString(), anyString())).thenReturn(clientRequest); + when(clientRequest.content(any())).thenReturn(clientRequest); + when(clientRequest.timeout(anyLong(), any())).thenReturn(clientRequest); + ContentResponse response = mock(ContentResponse.class); + when(response.getStatus()).thenReturn(tokenResponseCode); + String tokenResponse = FileReader.readFileInString("src/test/resources/json/TokenResponse.json"); + when(response.getContentAsString()).thenReturn(tokenResponse); + when(clientRequest.send()).thenReturn(response); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + fail(e.getMessage()); + } + return httpClient; } @Test void testInvalidConfig() { BridgeImpl bi = new BridgeImpl(new ThingTypeUID("test", "account"), "MB"); Map config = new HashMap<>(); - config.put("callbackIP", "999.999.999.999"); - config.put("callbackPort", "99999"); + config.put("refreshToken", Constants.JUNIT_REFRESH_TOKEN); bi.setConfiguration(new Configuration(config)); - AccountHandlerMock ahm = new AccountHandlerMock(bi, null); + AccountHandlerMock ahm = new AccountHandlerMock(bi, null, getHttpClient(404)); ThingCallbackListener tcl = new ThingCallbackListener(); ahm.setCallback(tcl); ahm.initialize(); @@ -83,6 +107,7 @@ class StatusTests { tcl = new ThingCallbackListener(); ahm.setCallback(tcl); ahm.initialize(); + ahm.refreshToken(); tsi = tcl.getThingStatus(); assertEquals(ThingStatus.OFFLINE, tsi.getStatus(), "Auth offline"); assertEquals(ThingStatusDetail.COMMUNICATION_ERROR, tsi.getStatusDetail(), "Auth detail"); @@ -107,13 +132,13 @@ class StatusTests { config.put("refreshInterval", Integer.MAX_VALUE); config.put("region", "row"); config.put("email", "a@b.c"); - config.put("callbackIP", "999.999.999.999"); - config.put("callbackPort", "99999"); + config.put("refreshToken", "abc"); bi.setConfiguration(new Configuration(config)); - AccountHandlerMock ahm = new AccountHandlerMock(bi, null); + AccountHandlerMock ahm = new AccountHandlerMock(bi, null, getHttpClient(404)); ThingCallbackListener tcl = new ThingCallbackListener(); ahm.setCallback(tcl); ahm.initialize(); + ahm.refreshToken(); ThingStatusInfo tsi = tcl.getThingStatus(); assertEquals(ThingStatus.OFFLINE, tsi.getStatus(), "Auth Offline"); assertEquals(ThingStatusDetail.COMMUNICATION_ERROR, tsi.getStatusDetail(), "Auth details"); @@ -140,17 +165,10 @@ class StatusTests { config.put("refreshInterval", Integer.MAX_VALUE); config.put("region", "row"); config.put("email", "a@b.c"); - config.put("callbackIP", "999.999.999.999"); - config.put("callbackPort", "99999"); + config.put("refreshToken", "abc"); bi.setConfiguration(new Configuration(config)); - AccessTokenResponse token = new AccessTokenResponse(); - token.setExpiresIn(3000); - token.setAccessToken(Constants.JUNIT_TOKEN); - token.setRefreshToken(Constants.JUNIT_REFRESH_TOKEN); - token.setCreatedOn(Instant.now()); - token.setTokenType("Bearer"); - token.setScope(Constants.SCOPE); - AccountHandlerMock ahm = new AccountHandlerMock(bi, Utils.toString(token)); + String tokenResponse = FileReader.readFileInString("src/test/resources/json/TokenResponse.json"); + AccountHandlerMock ahm = new AccountHandlerMock(bi, tokenResponse, getHttpClient(200)); ThingCallbackListener tcl = new ThingCallbackListener(); ahm.setCallback(tcl); ahm.initialize(); diff --git a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/AccountHandlerMock.java b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/AccountHandlerMock.java index b80f81190aa..48f6939ef27 100644 --- a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/AccountHandlerMock.java +++ b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/AccountHandlerMock.java @@ -15,7 +15,6 @@ package org.openhab.binding.mercedesme.internal.handler; import static org.mockito.Mockito.mock; import java.util.Locale; -import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -26,7 +25,6 @@ import org.openhab.binding.mercedesme.internal.Constants; import org.openhab.binding.mercedesme.internal.config.AccountConfiguration; import org.openhab.binding.mercedesme.internal.discovery.MercedesMeDiscoveryService; import org.openhab.core.i18n.LocaleProvider; -import org.openhab.core.net.NetworkAddressService; import org.openhab.core.storage.Storage; import org.openhab.core.storage.StorageService; import org.openhab.core.test.storage.VolatileStorageService; @@ -54,18 +52,17 @@ public class AccountHandlerMock extends AccountHandler { public AccountHandlerMock() { super(mock(Bridge.class), mock(MercedesMeDiscoveryService.class), mock(HttpClient.class), - mock(LocaleProvider.class), mock(StorageService.class), mock(NetworkAddressService.class)); - config = Optional.of(new AccountConfiguration()); + mock(LocaleProvider.class), mock(StorageService.class)); + config = new AccountConfiguration(); } - public AccountHandlerMock(Bridge b, @Nullable String storedObject) { - super(b, mock(MercedesMeDiscoveryService.class), mock(HttpClient.class), localeProvider, storageService, - mock(NetworkAddressService.class)); + public AccountHandlerMock(Bridge b, @Nullable String storedObject, HttpClient httpClient) { + super(b, mock(MercedesMeDiscoveryService.class), httpClient, localeProvider, storageService); if (storedObject != null) { Storage storage = storageService.getStorage(Constants.BINDING_ID); storage.put("a@b.c", storedObject); } - config = Optional.of(new AccountConfiguration()); + config = new AccountConfiguration(); } @Override @@ -94,4 +91,8 @@ public class AccountHandlerMock extends AccountHandler { public void connect() { super.ws.onConnect(mock(Session.class)); } + + public void refreshToken() { + authService.get().getToken(); + } } diff --git a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandlerTest.java b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandlerTest.java index aa44f22512f..0fb80814bc6 100644 --- a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandlerTest.java +++ b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandlerTest.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.openhab.binding.mercedesme.FileReader; import org.openhab.binding.mercedesme.internal.Constants; @@ -61,6 +62,11 @@ class VehicleHandlerTest { private static final int EVENT_STORAGE_COUNT = HVAC_UPDATE_COUNT + POSITIONING_UPDATE_COUNT + ECOSCORE_UPDATE_COUNT + 76; + @BeforeAll + public static void init() { + Utils.initialize(Utils.timeZoneProvider, Utils.localeProvider); + } + public static Map createBEV() { Thing thingMock = mock(Thing.class); when(thingMock.getThingTypeUID()).thenReturn(Constants.THING_TYPE_BEV); diff --git a/bundles/org.openhab.binding.mercedesme/src/test/resources/json/TokenResponse.json b/bundles/org.openhab.binding.mercedesme/src/test/resources/json/TokenResponse.json index 70e4aa0ab88..ad13e808925 100644 --- a/bundles/org.openhab.binding.mercedesme/src/test/resources/json/TokenResponse.json +++ b/bundles/org.openhab.binding.mercedesme/src/test/resources/json/TokenResponse.json @@ -1,9 +1,9 @@ { - "accessToken": "Tkn", - "tokenType": "Bearer", - "expiresIn": 7199, - "refreshToken": "RfrshTkn", + "access_token": "junitTestToken", + "token_type": "Bearer", + "expires_in": 7199, + "refresh_token": "RfrshTkn", "scope": "openid email phone profile offline_access ciam-uid", "state": null, - "createdOn": "2023-10-04T01:47:08.007038393Z" + "created_on": "2023-10-04T01:47:08.007038393Z" } \ No newline at end of file