1 module hunt.framework.auth.Identity; 2 3 import hunt.framework.auth.UserDetails; 4 import hunt.framework.auth.Claim; 5 import hunt.framework.auth.ClaimTypes; 6 import hunt.framework.auth.JwtToken; 7 import hunt.framework.auth.principal; 8 import hunt.framework.config.AuthUserConfig; 9 10 import hunt.http.AuthenticationScheme; 11 import hunt.logging; 12 import hunt.shiro; 13 14 import std.algorithm; 15 import std.array; 16 import std.base64; 17 import std.string; 18 import std.variant; 19 20 /** 21 * User Identity 22 */ 23 class Identity { 24 private Subject _subject; 25 private string _guardName; 26 private bool _isValid = false; 27 28 this(string guardName, bool isValid) { 29 _guardName = guardName; 30 _isValid = isValid; 31 } 32 33 bool isValid() nothrow { 34 return _isValid; 35 } 36 37 Subject subject() { 38 if(_isValid) { 39 if(_subject is null) { 40 _subject = SecurityUtils.getSubject(_guardName); 41 } 42 return _subject; 43 } else { 44 string msg = format("This identity [%s] is invalid!", _guardName); 45 error(msg); 46 return null; 47 // throw new AuthorizationException(msg); 48 } 49 } 50 51 UserDetails userDetails() nothrow { 52 UserDetails userDetails = null; 53 try { 54 Subject s = subject(); 55 if(s is null) { 56 UserDetails u = new UserDetails(); 57 u.name = "Wrong use name"; 58 return u; 59 } 60 userDetails = cast(UserDetails)s.getPrincipal(); 61 } catch(Exception ex) { 62 warning(ex.msg); 63 version(HUNT_AUTH_DEBUG) warning(ex); 64 } 65 return userDetails; 66 } 67 68 ulong id() nothrow { 69 UserDetails userDetails = userDetails(); 70 if(userDetails !is null) { 71 return userDetails.id; 72 } 73 return 0; 74 } 75 76 string name() nothrow { 77 UserDetails userDetails = userDetails(); 78 if(userDetails !is null) { 79 return userDetails.name; 80 } 81 return ""; 82 } 83 84 AuthenticationScheme authScheme() { 85 Variant var = claim(ClaimTypes.AuthScheme); 86 if(var == null) return AuthenticationScheme.None; 87 return cast(AuthenticationScheme)var.get!string(); 88 } 89 90 string fullName() { 91 return claimAs!(string)(ClaimTypes.FullName); 92 } 93 94 Variant claim(string type) { 95 Variant v = Variant(null); 96 UserDetails userDetails = userDetails(); 97 if(userDetails !is null) { 98 v = userDetails.claim(type); 99 } 100 101 return v; 102 } 103 104 T claimAs(T)(string type) { 105 Variant v = claim(type); 106 if(v == null || !v.hasValue()) { 107 return T.init; 108 } 109 110 return v.get!T(); 111 } 112 113 Claim[] claims() { 114 Claim[] r; 115 116 UserDetails userDetails = userDetails(); 117 if(userDetails !is null) { 118 r = userDetails.claims(); 119 } 120 121 return r; 122 } 123 124 void authenticate(string username, string password, bool remember = true, 125 string tokenName = DEFAULT_AUTH_TOKEN_NAME) { 126 version(HUNT_DEBUG) { 127 infof("Authenticating for [%s]", username); 128 } 129 130 Subject _subject = subject(); 131 if(_subject is null) { 132 string msg = format("Failed to authenticate for %s", username); 133 errorf(msg); 134 throw new AuthenticationException(msg); 135 // return; 136 } 137 138 version(HUNT_AUTH_DEBUG) { 139 tracef("Checking the status at first: %s", _subject.isAuthenticated()); 140 } 141 142 if (_subject.isAuthenticated()) { 143 _subject.logout(); 144 } 145 146 UsernamePasswordToken token = new UsernamePasswordToken(username, password); 147 token.setRememberMe(remember); 148 token.name = tokenName; 149 150 try { 151 _subject.login(token); 152 } catch (UnknownAccountException ex) { 153 info("There is no user with username of " ~ token.getPrincipal()); 154 } catch (IncorrectCredentialsException ex) { 155 info("Password for account " ~ token.getPrincipal() ~ " was incorrect!"); 156 } catch (LockedAccountException ex) { 157 info("The account for username " ~ token.getPrincipal() 158 ~ " is locked. " ~ "Please contact your administrator to unlock it."); 159 } catch (AuthenticationException ex) { 160 errorf("Authentication failed: ", ex.msg); 161 version(HUNT_DEBUG) error(ex); 162 } catch (Exception ex) { 163 errorf("Authentication failed: ", ex.msg); 164 version(HUNT_DEBUG) error(ex); 165 } 166 } 167 168 void authenticate(string token, AuthenticationScheme scheme) { 169 version(HUNT_AUTH_DEBUG) { 170 infof("scheme: %s", scheme); 171 } 172 173 if(scheme == AuthenticationScheme.Bearer) { 174 bearerLogin(token); 175 } else if(scheme == AuthenticationScheme.Basic) { 176 basicLogin(token); 177 } else { 178 warningf("Unknown AuthenticationScheme: %s", scheme); 179 } 180 } 181 182 private void basicLogin(string tokenString) { 183 ubyte[] decoded = Base64.decode(tokenString); 184 string[] values = split(cast(string)decoded, ":"); 185 if(values.length != 2) { 186 warningf("Wrong token: %s", values); 187 return; 188 } 189 190 string username = values[0]; 191 string password = values[1]; 192 authenticate(username, password, true); 193 } 194 195 private void bearerLogin(string tokenString) { 196 try { 197 JwtToken token = new JwtToken(tokenString); 198 subject().login(token); 199 } catch (AuthenticationException e) { 200 warning(e.msg); 201 version(HUNT_AUTH_DEBUG) warning(e); 202 } catch(Exception ex) { 203 warning(ex.msg); 204 version(HUNT_DEBUG) warning(ex); 205 } 206 } 207 208 bool login(AuthenticationToken token) { 209 Subject sj = subject(); 210 version(HUNT_AUTH_DEBUG) { 211 tracef("Checking the status at first: %s", _subject.isAuthenticated()); 212 } 213 214 if (sj.isAuthenticated()) { 215 sj.logout(); 216 } 217 sj.logout(); 218 219 if(token is null) { 220 warning("The token is null"); 221 return false; 222 } 223 224 225 try { 226 // FIXME: Needing refactor or cleanup -@zhangxueping at 2020-08-01T16:13:55+08:00 227 // Session with id [b82d265a-ec96-406b-854c-0a846e690aff] has expired 228 sj.login(token); 229 } catch (AuthenticationException e) { 230 version(HUNT_DEBUG) warning(e.msg); 231 version(HUNT_AUTH_DEBUG) warning(e); 232 } catch(Exception ex) { 233 version(HUNT_DEBUG) warning(ex.msg); 234 version(HUNT_AUTH_DEBUG) warning(ex); 235 } 236 237 return sj.isAuthenticated(); 238 } 239 240 bool isAuthenticated() nothrow { 241 try { 242 Subject s = subject(); 243 if(s is null) return false; 244 return s.isAuthenticated(); 245 } catch(Exception ex) { 246 warning(ex.msg); 247 version(HUNT_DEBUG) warning(ex); 248 return false; 249 } 250 } 251 252 bool hasRole(string role) nothrow { 253 try { 254 Subject s = subject(); 255 if(s is null) return false; 256 return s.hasRole(role); 257 } catch(Exception ex) { 258 warning(ex.msg); 259 version(HUNT_DEBUG) warning(ex); 260 return false; 261 } 262 } 263 264 bool hasAllRoles(string[] roles...) nothrow { 265 try { 266 Subject s = subject(); 267 if(s is null) return false; 268 return s.hasAllRoles(roles); 269 } catch(Exception ex) { 270 warning(ex.msg); 271 version(HUNT_DEBUG) warning(ex); 272 return false; 273 } 274 } 275 276 bool isPermitted(string[] permissions...) nothrow { 277 278 // Try to convert all the custom permissions to shiro's ones 279 try { 280 Subject s = subject(); 281 if(s is null) return false; 282 283 string[] shiroPermissions = permissions.map!(p => p.strip().toShiroPermissions()).array; 284 bool[] resultSet = s.isPermitted(shiroPermissions); 285 foreach(bool r; resultSet ) { 286 if(!r) return false; 287 } 288 } catch(Exception ex) { 289 warning(ex.msg); 290 version(HUNT_DEBUG) warning(ex); 291 return false; 292 } 293 294 return true; 295 } 296 297 void touchSession() nothrow { 298 try { 299 300 Subject s = subject(); 301 if(s is null) return; 302 303 Session session = s.getSession(false); 304 if (session !is null) { 305 try { 306 session.touch(); 307 } catch (Throwable t) { 308 error("session.touch() method invocation has failed. Unable to update " ~ 309 "the corresponding session's last access time based on the incoming request."); 310 error(t.msg); 311 version(HUNT_AUTH_DEBUG) warning(t); 312 } 313 } 314 } catch(Exception ex) { 315 warning(ex.msg); 316 version(HUNT_DEBUG) warning(ex); 317 } 318 } 319 320 /** 321 * It should be called from the Auth 322 */ 323 void logout() nothrow { 324 try { 325 Subject s = subject(); 326 if(s is null) return; 327 s.logout(); 328 } catch(Exception ex) { 329 warning(ex.msg); 330 version(HUNT_DEBUG) warning(ex); 331 } 332 } 333 334 override string toString() { 335 return name(); 336 } 337 }