1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 module hunt.shiro.realm.SimpleAccountRealm;
20 
21 import hunt.shiro.realm.AuthorizingRealm;
22 
23 import hunt.shiro.Exceptions;
24 import hunt.shiro.authc.AuthenticationInfo;
25 import hunt.shiro.authc.AuthenticationToken;
26 import hunt.shiro.authc.SimpleAccount;
27 import hunt.shiro.authc.UsernamePasswordToken;
28 import hunt.shiro.authz.AuthorizationInfo;
29 import hunt.shiro.authz.permission.Permission;
30 import hunt.shiro.authz.SimpleRole;
31 import hunt.shiro.subject.PrincipalCollection;
32 import hunt.shiro.util.CollectionUtils;
33 
34 import hunt.collection.HashSet;
35 import hunt.collection.LinkedHashMap;
36 import hunt.collection.Map;
37 import hunt.collection.Set;
38 import hunt.Exceptions;
39 import hunt.String;
40 
41 import core.sync.rwmutex; // ReentrantReadWriteLock
42 
43 import std.array;
44 import std.string;
45 
46 /**
47  * A simple implementation of the {@link Realm Realm} interface that
48  * uses a set of configured user accounts and roles to support authentication and authorization.  Each account entry
49  * specifies the username, password, and roles for a user.  Roles can also be mapped
50  * to permissions and associated with users.
51  * <p/>
52  * User accounts and roles are stored in two {@code Map}s in memory, so it is expected that the total number of either
53  * is not sufficiently large.
54  *
55  */
56 class SimpleAccountRealm : AuthorizingRealm {
57 
58     //TODO - complete JavaDoc
59     protected Map!(string, SimpleAccount) users; //username-to-SimpleAccount
60     protected Map!(string, SimpleRole) roles; //roleName-to-SimpleRole
61     protected ReadWriteMutex USERS_LOCK;
62     protected ReadWriteMutex ROLES_LOCK;
63 
64     this() {
65         initialize();
66     }
67 
68     this(string name) {
69         initialize();
70         setName(name);
71     }
72 
73     private void initialize() {
74         this.users = new LinkedHashMap!(string, SimpleAccount)();
75         this.roles = new LinkedHashMap!(string, SimpleRole)();
76         USERS_LOCK = new ReadWriteMutex();
77         ROLES_LOCK = new ReadWriteMutex();
78         //SimpleAccountRealms are memory-only realms - no need for an additional cache mechanism since we're
79         //already as memory-efficient as we can be:
80         setCachingEnabled(false);
81     }
82 
83     protected SimpleAccount getUser(string username) {
84         USERS_LOCK.reader().lock();
85         try {
86             return this.users.get(username);
87         } finally {
88             USERS_LOCK.reader().unlock();
89         }
90     }
91 
92     bool accountExists(string username) {
93         return getUser(username) !is null;
94     }
95 
96     void addAccount(string username, string password) {
97         addAccount(username, password, cast(string[]) null);
98     }
99 
100     void addAccount(string username, string password, string[] roles...) {
101         Set!(string) roleNames = CollectionUtils.asSet(roles);
102         SimpleAccount account = new SimpleAccount(new String(username), new String(password), getName(), roleNames, cast(Set!(Permission))null);
103         add(account);
104     }
105 
106     protected string getUsername(SimpleAccount account) {
107         return getUsername(account.getPrincipals());
108     }
109 
110     protected string getUsername(PrincipalCollection principals) {
111         return getAvailablePrincipal(principals).toString();
112     }
113 
114     protected void add(SimpleAccount account) {
115         string username = getUsername(account);
116         USERS_LOCK.writer().lock();
117         try {
118             this.users.put(username, account);
119         } finally {
120             USERS_LOCK.writer().unlock();
121         }
122     }
123 
124     protected SimpleRole getRole(string rolename) {
125         ROLES_LOCK.reader().lock();
126         try {
127             return roles.get(rolename);
128         } finally {
129             ROLES_LOCK.reader().unlock();
130         }
131     }
132 
133     bool roleExists(string name) {
134         return getRole(name) !is null;
135     }
136 
137     void addRole(string name) {
138         add(new SimpleRole(name));
139     }
140 
141     protected void add(SimpleRole role) {
142         ROLES_LOCK.writer().lock();
143         try {
144             roles.put(role.getName(), role);
145         } finally {
146             ROLES_LOCK.writer().unlock();
147         }
148     }
149 
150     protected static Set!(string) toSet(string delimited, string delimiter) {
151         if (delimited.empty) {
152             return null;
153         }
154 
155         Set!(string) values = new HashSet!(string)();
156         string[] rolenamesArray = delimited.split(delimiter);
157         foreach(string s ; rolenamesArray) {
158             string trimmed = s.strip();
159             if (trimmed.length > 0) {
160                 values.add(trimmed);
161             }
162         }
163 
164         return values;
165     }
166 
167     override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token){
168         UsernamePasswordToken upToken = cast(UsernamePasswordToken) token;
169         SimpleAccount account = getUser(upToken.getUsername());
170 
171         if (account !is null) {
172 
173             if (account.isLocked()) {
174                 throw new LockedAccountException("Account [" ~ account.toString() ~ "] is locked.");
175             }
176             if (account.isCredentialsExpired()) {
177                 string msg = "The credentials for account [" ~ account.toString() ~ "] are expired";
178                 throw new ExpiredCredentialsException(msg);
179             }
180 
181         }
182 
183         return account;
184     }
185 
186     override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
187         string username = getUsername(principals);
188         USERS_LOCK.reader().lock();
189         try {
190             return this.users.get(username);
191         } finally {
192             USERS_LOCK.reader().unlock();
193         }
194     }
195 }