1414
1515namespace Staffbase \plugins \sdk ;
1616
17- use Lcobucci \JWT \Token ;
18- use Lcobucci \JWT \Parser ;
17+ use DateInterval ;
18+ use Lcobucci \Clock \SystemClock ;
19+ use Lcobucci \JWT \Configuration ;
1920use Lcobucci \JWT \Signer \Key ;
20- use Lcobucci \JWT \ValidationData ;
21- use Lcobucci \JWT \Claim \Validatable ;
21+ use Lcobucci \JWT \Token ;
22+ use Lcobucci \JWT \Signer \Key \InMemory ;
23+ use Lcobucci \JWT \Validation \Constraint \SignedWith ;
24+ use Lcobucci \JWT \Validation \Constraint \StrictValidAt ;
25+ use Lcobucci \JWT \Validation \RequiredConstraintsViolated ;
2226use Lcobucci \JWT \Signer \Rsa \Sha256 ;
2327use Staffbase \plugins \sdk \Exceptions \SSOException ;
2428use Staffbase \plugins \sdk \Exceptions \SSOAuthenticationException ;
29+ use Staffbase \plugins \sdk \Validation \HasInstanceId ;
2530
2631/**
2732 * A container which is able to decrypt and store the data transmitted
@@ -32,8 +37,17 @@ class SSOToken extends SSOData
3237 /**
3338 * @var Token $token
3439 */
35- private $ token = null ;
40+ private ?Token $ token = null ;
41+
42+ /**
43+ * @var Key $key
44+ */
45+ private Key $ key ;
3646
47+ /**
48+ * @var Configuration $config
49+ */
50+ private Configuration $ config ;
3751 /**
3852 * Constructor
3953 *
@@ -43,55 +57,75 @@ class SSOToken extends SSOData
4357 *
4458 * @throws SSOException on invalid parameters.
4559 */
46- public function __construct ($ appSecret , $ tokenData , $ leeway = 0 ) {
60+ public function __construct (string $ appSecret , string $ tokenData , ? int $ leeway = 0 ) {
4761
4862 if (!trim ($ appSecret ))
4963 throw new SSOException ('Parameter appSecret for SSOToken is empty. ' );
5064
5165 if (!trim ($ tokenData ))
5266 throw new SSOException ('Parameter tokenData for SSOToken is empty. ' );
5367
54- if (!is_numeric ($ leeway ))
55- throw new SSOException ('Parameter leeway has to be numeric. ' );
56-
57- // convert secret to PEM if its a plain base64 string and does not yield an url
58- if (strpos (trim ($ appSecret ),'----- ' ) !== 0 && strpos (trim ($ appSecret ), 'file:// ' ) !==0 )
59- $ appSecret = self ::base64ToPEMPublicKey ($ appSecret );
68+ $ this ->key = $ this ->getKey (trim ($ appSecret ));
69+ $ this ->config = Configuration::forSymmetricSigner (new Sha256 (), $ this ->key );
6070
61- $ this ->parseToken ($ appSecret , $ tokenData , $ leeway );
71+ $ this ->parseToken ($ tokenData , $ leeway );
6272 }
6373
6474 /**
6575 * Creates and validates an SSO token.
6676 *
67- * @param string $appSecret Either a PEM formatted key or a file:// URL of the same.
6877 * @param string $tokenData The token text.
6978 * @param int $leeway count of seconds added to current timestamp
7079 *
7180 * @throws SSOAuthenticationException if the parsing/verification/validation of the token fails.
7281 */
73- protected function parseToken ($ appSecret , $ tokenData , $ leeway ) {
74-
82+ protected function parseToken (string $ tokenData , int $ leeway ) {
7583 // parse text
76- $ this ->token = (new Parser ())->parse ((string ) $ tokenData );
77-
78- // verify signature
79- $ signer = new Sha256 ();
80- $ key = new Key ($ appSecret );
84+ $ this ->token = $ this ->config ->parser ()->parse ($ tokenData );
85+
86+ $ constrains = [
87+ new StrictValidAt (SystemClock::fromUTC (), $ this ->getLeewayInterval ($ leeway )),
88+ new SignedWith (new Sha256 (),$ this ->key ),
89+ new HasInstanceId ()
90+ ];
91+
92+ try {
93+ $ this ->config ->validator ()->assert ($ this ->token , ...$ constrains );
94+ } catch (RequiredConstraintsViolated $ violation ) {
95+ throw new SSOAuthenticationException ($ violation ->getMessage ());
96+ }
97+ }
8198
82- if (!$ this ->token ->verify ($ signer , $ key ))
83- throw new SSOAuthenticationException ('Token verification failed. ' );
99+ /**
100+ * Test if a claim is set.
101+ *
102+ * @param string $claim name.
103+ *
104+ * @return boolean
105+ */
106+ protected function hasClaim ($ claim ) {
107+ return $ this ->token ->claims ()->has ($ claim );
108+ }
84109
85- // validate claims
86- $ data = new ValidationData (time (), $ leeway ); // iat, nbf and exp are validated by default
110+ /**
111+ * Get a claim without checking for existence.
112+ *
113+ * @param string $claim name.
114+ *
115+ * @return mixed
116+ */
117+ protected function getClaim ($ claim ) {
118+ return $ this ->token ->claims ()->get ($ claim );
119+ }
87120
88- if (!$ this ->token ->validate ($ data )) {
89- $ this ->throwVerboseException ($ data );
90- }
121+ /**
122+ * Get an array of all available claims and their values.
123+ *
124+ * @return array
125+ */
126+ protected function getAllClaims () {
91127
92- // its a security risk to work with tokens lacking instance id
93- if (!trim ($ this ->getInstanceId ()))
94- throw new SSOAuthenticationException ('Token lacks instance id. ' );
128+ return $ this ->token ->claims ()->all ();
95129 }
96130
97131 /**
@@ -101,7 +135,7 @@ protected function parseToken($appSecret, $tokenData, $leeway) {
101135 *
102136 * @return string PEM encoded key
103137 */
104- public static function base64ToPEMPublicKey ($ data ) {
138+ public static function base64ToPEMPublicKey (string $ data ): string {
105139
106140 $ data = strtr ($ data , array (
107141 "\r" => "" ,
@@ -115,80 +149,39 @@ public static function base64ToPEMPublicKey($data) {
115149 }
116150
117151 /**
118- * Validate the token with more verbose exceptions
152+ * Decides between the new key methods, the JWT library offers
119153 *
120- * Due to minor shortcomings of the library we have to redo the validation
121- * manually to get the reason for the failure and propagate it.
122- * We emulate the validation process for the v3.x of the library.
123- *
124- * This will most likely have to change on library upgrade either
125- * by using then supported verbosity or reimplementing validation
126- * as done in the new flow.
127- *
128- * @param ValidationData $data to validate against
129- *
130- * @throws SSOAuthenticationException always.
154+ * @param string $appSecret
155+ * @return Key
131156 */
132- protected function throwVerboseException (ValidationData $ data ) {
133-
134- foreach ($ this ->token ->getClaims () as $ claim ) {
135- if ($ claim instanceof Validatable) {
136- if (!$ claim ->validate ($ data )) {
137-
138- $ claimName = $ claim ->getName ();
139- $ claimValue = $ claim ->getValue ();
140-
141- // get the short class-name of the validatable claim
142- $ segments = explode ('\\' , get_class ($ claim ));
143- $ operator = array_pop ($ segments );
144- $ operand = $ data ->get ($ claimName );
145-
146- throw new SSOAuthenticationException ("Token Validation failed on claim ' $ claimName' $ claimValue $ operator $ operand. " );
147- }
148- }
157+ private function getKey (string $ appSecret ): Key {
158+ if (strpos ($ appSecret ,'----- ' ) === 0 ) {
159+ $ key = InMemory::plainText ($ appSecret );
160+ } else if (strpos ($ appSecret , 'file:// ' ) === 0 ) {
161+ $ key = InMemory::file ($ appSecret );
162+ } else {
163+ $ key = InMemory::plainText ($ this ->base64ToPEMPublicKey ($ appSecret ));
149164 }
150-
151- // unknown reason, probably an addition to used library
152- throw new SSOAuthenticationException ('Token Validation failed. ' );
165+ return $ key ;
153166 }
154167
155168 /**
156- * Test if a claim is set.
169+ * Formats the leeway integer value into a DateInterval as this is
170+ * needed by the JWT library
157171 *
158- * @param string $claim name.
159- *
160- * @return boolean
161- */
162- protected function hasClaim ($ claim ) {
163-
164- return $ this ->token ->hasClaim ($ claim );
165- }
166-
167- /**
168- * Get a claim without checking for existence.
169- *
170- * @param string $claim name.
171- *
172- * @return mixed
173- */
174- protected function getClaim ($ claim ) {
175-
176- return $ this ->token ->getClaim ($ claim );
177- }
178-
179- /**
180- * Get an array of all available claims and their values.
181- *
182- * @return array
172+ * @param int $leeway count of seconds added to current timestamp
173+ * @return DateInterval DateInterval
183174 */
184- protected function getAllClaims () {
185-
186- $ res = [];
187- $ claims = $ this ->token ->getClaims ();
188-
189- foreach ($ claims as $ claim )
190- $ res [$ claim ->getName ()] = $ claim ->getValue ();
175+ private function getLeewayInterval (int $ leeway ): DateInterval {
176+ $ leewayInterval = "PT {$ leeway }S " ;
177+
178+ try {
179+ $ interval = new DateInterval ($ leewayInterval );
180+ } catch (\Exception $ e ) {
181+ error_log ("Wrong date interval $ leewayInterval " );
182+ $ interval = new DateInterval ('PT0S ' );
183+ }
191184
192- return $ res ;
185+ return $ interval ;
193186 }
194187}
0 commit comments