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