LoginContext.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package javax.security.auth.login;
  18. import java.io.IOException;
  19. import java.security.AccessController;
  20. import java.security.AccessControlContext;
  21. import java.security.PrivilegedExceptionAction;
  22. import java.security.PrivilegedActionException;
  23. import java.security.Security;
  24. import java.util.HashMap;
  25. import java.util.Map;
  26. import javax.security.auth.Subject;
  27. import javax.security.auth.callback.CallbackHandler;
  28. import javax.security.auth.callback.Callback;
  29. import javax.security.auth.callback.UnsupportedCallbackException;
  30. import javax.security.auth.spi.LoginModule;
  31. import javax.security.auth.AuthPermission;
  32. import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
  33. import org.apache.harmony.auth.internal.nls.Messages;
  34. public class LoginContext {
  35. private static final String DEFAULT_CALLBACK_HANDLER_PROPERTY = "auth.login.defaultCallbackHandler"; //$NON-NLS-1$
  36. /*
  37. * Integer constants which serve as a replacement for the corresponding
  38. * LoginModuleControlFlag.* constants. These integers are used later as
  39. * index in the arrays - see loginImpl() and logoutImpl() methods
  40. */
  41. private static final int OPTIONAL = 0;
  42. private static final int REQUIRED = 1;
  43. private static final int REQUISITE = 2;
  44. private static final int SUFFICIENT = 3;
  45. // Subject to be used for this LoginContext's operations
  46. private Subject subject;
  47. /*
  48. * Shows whether the subject was specified by user (true) or was created by
  49. * this LoginContext itself (false).
  50. */
  51. private boolean userProvidedSubject;
  52. // Shows whether we use installed or user-provided Configuration
  53. private boolean userProvidedConfig;
  54. // An user's AccessControlContext, used when user specifies
  55. private AccessControlContext userContext;
  56. /*
  57. * Either a callback handler passed by the user or a wrapper for the user's
  58. * specified handler - see init() below.
  59. */
  60. private CallbackHandler callbackHandler;
  61. /*
  62. * An array which keeps the instantiated and init()-ialized login modules
  63. * and their states
  64. */
  65. private Module[] modules;
  66. // Stores a shared state
  67. private Map<String, ?> sharedState;
  68. // A context class loader used to load [mainly] LoginModules
  69. private ClassLoader contextClassLoader;
  70. // Shows overall status - whether this LoginContext was successfully logged
  71. private boolean loggedIn;
  72. public LoginContext(String name) throws LoginException {
  73. super();
  74. init(name, null, null, null);
  75. }
  76. public LoginContext(String name, CallbackHandler cbHandler) throws LoginException {
  77. super();
  78. if (cbHandler == null) {
  79. throw new LoginException(Messages.getString("auth.34")); //$NON-NLS-1$
  80. }
  81. init(name, null, cbHandler, null);
  82. }
  83. public LoginContext(String name, Subject subject) throws LoginException {
  84. super();
  85. if (subject == null) {
  86. throw new LoginException(Messages.getString("auth.03")); //$NON-NLS-1$
  87. }
  88. init(name, subject, null, null);
  89. }
  90. public LoginContext(String name, Subject subject, CallbackHandler cbHandler)
  91. throws LoginException {
  92. super();
  93. if (subject == null) {
  94. throw new LoginException(Messages.getString("auth.03")); //$NON-NLS-1$
  95. }
  96. if (cbHandler == null) {
  97. throw new LoginException(Messages.getString("auth.34")); //$NON-NLS-1$
  98. }
  99. init(name, subject, cbHandler, null);
  100. }
  101. public LoginContext(String name, Subject subject, CallbackHandler cbHandler,
  102. Configuration config) throws LoginException {
  103. super();
  104. init(name, subject, cbHandler, config);
  105. }
  106. // Does all the machinery needed for the initialization.
  107. private void init(String name, Subject subject, final CallbackHandler cbHandler,
  108. Configuration config) throws LoginException {
  109. userProvidedSubject = (this.subject = subject) != null;
  110. //
  111. // Set config
  112. //
  113. if (name == null) {
  114. throw new LoginException(Messages.getString("auth.00")); //$NON-NLS-1$
  115. }
  116. if (config == null) {
  117. config = Configuration.getAccessibleConfiguration();
  118. } else {
  119. userProvidedConfig = true;
  120. }
  121. SecurityManager sm = System.getSecurityManager();
  122. if (sm != null && !userProvidedConfig) {
  123. sm.checkPermission(new AuthPermission("createLoginContext." + name));//$NON-NLS-1$
  124. }
  125. AppConfigurationEntry[] entries = config.getAppConfigurationEntry(name);
  126. if (entries == null) {
  127. if (sm != null && !userProvidedConfig) {
  128. sm.checkPermission(new AuthPermission("createLoginContext.other")); //$NON-NLS-1$
  129. }
  130. entries = config.getAppConfigurationEntry("other"); //$NON-NLS-1$
  131. if (entries == null) {
  132. throw new LoginException(Messages.getString("auth.35", name)); //$NON-NLS-1$
  133. }
  134. }
  135. modules = new Module[entries.length];
  136. for (int i = 0; i < modules.length; i++) {
  137. modules[i] = new Module(entries[i]);
  138. }
  139. //
  140. // Set CallbackHandler and this.contextClassLoader
  141. //
  142. /*
  143. * as some of the operations to be executed (i.e. get*ClassLoader,
  144. * getProperty, class loading) are security-checked, then combine all of
  145. * them into a single doPrivileged() call.
  146. */
  147. try {
  148. AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
  149. public Void run() throws Exception {
  150. // First, set the 'contextClassLoader'
  151. contextClassLoader = Thread.currentThread().getContextClassLoader();
  152. if (contextClassLoader == null) {
  153. contextClassLoader = ClassLoader.getSystemClassLoader();
  154. }
  155. // then, checks whether the cbHandler is set
  156. if (cbHandler == null) {
  157. // well, let's try to find it
  158. String klassName = Security
  159. .getProperty(DEFAULT_CALLBACK_HANDLER_PROPERTY);
  160. if (klassName == null || klassName.length() == 0) {
  161. return null;
  162. }
  163. Class<?> klass = Class.forName(klassName, true, contextClassLoader);
  164. callbackHandler = (CallbackHandler) klass.newInstance();
  165. } else {
  166. callbackHandler = cbHandler;
  167. }
  168. return null;
  169. }
  170. });
  171. } catch (PrivilegedActionException ex) {
  172. Throwable cause = ex.getCause();
  173. throw (LoginException) new LoginException(Messages.getString("auth.36")).initCause(cause);//$NON-NLS-1$
  174. }
  175. if (userProvidedConfig) {
  176. userContext = AccessController.getContext();
  177. } else if (callbackHandler != null) {
  178. userContext = AccessController.getContext();
  179. callbackHandler = new ContextedCallbackHandler(callbackHandler);
  180. }
  181. }
  182. public Subject getSubject() {
  183. if (userProvidedSubject || loggedIn) {
  184. return subject;
  185. }
  186. return null;
  187. }
  188. /**
  189. * Warning: calling the method more than once may result in undefined
  190. * behaviour if logout() method is not invoked before.
  191. */
  192. public void login() throws LoginException {
  193. PrivilegedExceptionAction<Void> action = new PrivilegedExceptionAction<Void>() {
  194. public Void run() throws LoginException {
  195. loginImpl();
  196. return null;
  197. }
  198. };
  199. try {
  200. if (userProvidedConfig) {
  201. AccessController.doPrivileged(action, userContext);
  202. } else {
  203. AccessController.doPrivileged(action);
  204. }
  205. } catch (PrivilegedActionException ex) {
  206. throw (LoginException) ex.getException();
  207. }
  208. }
  209. /**
  210. * The real implementation of login() method whose calls are wrapped into
  211. * appropriate doPrivileged calls in login().
  212. */
  213. private void loginImpl() throws LoginException {
  214. if (subject == null) {
  215. subject = new Subject();
  216. }
  217. if (sharedState == null) {
  218. sharedState = new HashMap<String, Object>();
  219. }
  220. // PHASE 1: Calling login()-s
  221. Throwable firstProblem = null;
  222. int[] logged = new int[4];
  223. int[] total = new int[4];
  224. for (Module module : modules) {
  225. try {
  226. // if a module fails during Class.forName(), then it breaks overall
  227. // attempt - see catch() below
  228. module.create(subject, callbackHandler, sharedState);
  229. if (module.module.login()) {
  230. ++total[module.getFlag()];
  231. ++logged[module.getFlag()];
  232. if (module.getFlag() == SUFFICIENT) {
  233. break;
  234. }
  235. }
  236. } catch (Throwable ex) {
  237. if (firstProblem == null) {
  238. firstProblem = ex;
  239. }
  240. if (module.klass == null) {
  241. /*
  242. * an exception occurred during class lookup - overall
  243. * attempt must fail a little trick: increase the REQUIRED's
  244. * number - this will look like a failed REQUIRED module
  245. * later, so overall attempt will fail
  246. */
  247. ++total[REQUIRED];
  248. break;
  249. }
  250. ++total[module.getFlag()];
  251. // something happened after the class was loaded
  252. if (module.getFlag() == REQUISITE) {
  253. // ... and no need to walk down anymore
  254. break;
  255. }
  256. }
  257. }
  258. // end of PHASE1,
  259. // Let's decide whether we have either overall success or a total failure
  260. boolean fail = true;
  261. /*
  262. * Note: 'failed[xxx]!=0' is not enough to check.
  263. *
  264. * Use 'logged[xx] != total[xx]' instead. This is because some modules
  265. * might not be counted as 'failed' if an exception occurred during
  266. * preload()/Class.forName()-ing. But, such modules still get counted in
  267. * the total[].
  268. */
  269. // if any REQ* module failed - then it's failure
  270. if (logged[REQUIRED] != total[REQUIRED] || logged[REQUISITE] != total[REQUISITE]) {
  271. // fail = true;
  272. } else {
  273. if (total[REQUIRED] == 0 && total[REQUISITE] == 0) {
  274. // neither REQUIRED nor REQUISITE was configured.
  275. // must have at least one SUFFICIENT or OPTIONAL
  276. if (logged[OPTIONAL] != 0 || logged[SUFFICIENT] != 0) {
  277. fail = false;
  278. }
  279. //else { fail = true; }
  280. } else {
  281. fail = false;
  282. }
  283. }
  284. int commited[] = new int[4];
  285. // clear it
  286. total[0] = total[1] = total[2] = total[3] = 0;
  287. if (!fail) {
  288. // PHASE 2:
  289. for (Module module : modules) {
  290. if (module.klass != null) {
  291. ++total[module.getFlag()];
  292. try {
  293. module.module.commit();
  294. ++commited[module.getFlag()];
  295. } catch (Throwable ex) {
  296. if (firstProblem == null) {
  297. firstProblem = ex;
  298. }
  299. }
  300. }
  301. }
  302. }
  303. // need to decide once again
  304. fail = true;
  305. if (commited[REQUIRED] != total[REQUIRED] || commited[REQUISITE] != total[REQUISITE]) {
  306. //fail = true;
  307. } else {
  308. if (total[REQUIRED] == 0 && total[REQUISITE] == 0) {
  309. /*
  310. * neither REQUIRED nor REQUISITE was configured. must have at
  311. * least one SUFFICIENT or OPTIONAL
  312. */
  313. if (commited[OPTIONAL] != 0 || commited[SUFFICIENT] != 0) {
  314. fail = false;
  315. } else {
  316. //fail = true;
  317. }
  318. } else {
  319. fail = false;
  320. }
  321. }
  322. if (fail) {
  323. // either login() or commit() failed. aborting...
  324. for (Module module : modules) {
  325. try {
  326. module.module.abort();
  327. } catch ( /*LoginException*/Throwable ex) {
  328. if (firstProblem == null) {
  329. firstProblem = ex;
  330. }
  331. }
  332. }
  333. if (firstProblem instanceof PrivilegedActionException
  334. && firstProblem.getCause() != null) {
  335. firstProblem = firstProblem.getCause();
  336. }
  337. if (firstProblem instanceof LoginException) {
  338. throw (LoginException) firstProblem;
  339. }
  340. throw (LoginException) new LoginException(Messages.getString("auth.37")).initCause(firstProblem); //$NON-NLS-1$
  341. }
  342. loggedIn = true;
  343. }
  344. public void logout() throws LoginException {
  345. PrivilegedExceptionAction<Void> action = new PrivilegedExceptionAction<Void>() {
  346. public Void run() throws LoginException {
  347. logoutImpl();
  348. return null;
  349. }
  350. };
  351. try {
  352. if (userProvidedConfig) {
  353. AccessController.doPrivileged(action, userContext);
  354. } else {
  355. AccessController.doPrivileged(action);
  356. }
  357. } catch (PrivilegedActionException ex) {
  358. throw (LoginException) ex.getException();
  359. }
  360. }
  361. /**
  362. * The real implementation of logout() method whose calls are wrapped into
  363. * appropriate doPrivileged calls in logout().
  364. */
  365. private void logoutImpl() throws LoginException {
  366. if (subject == null) {
  367. throw new LoginException(Messages.getString("auth.38")); //$NON-NLS-1$
  368. }
  369. loggedIn = false;
  370. Throwable firstProblem = null;
  371. int total = 0;
  372. for (Module module : modules) {
  373. try {
  374. module.module.logout();
  375. ++total;
  376. } catch (Throwable ex) {
  377. if (firstProblem == null) {
  378. firstProblem = ex;
  379. }
  380. }
  381. }
  382. if (firstProblem != null || total == 0) {
  383. if (firstProblem instanceof PrivilegedActionException
  384. && firstProblem.getCause() != null) {
  385. firstProblem = firstProblem.getCause();
  386. }
  387. if (firstProblem instanceof LoginException) {
  388. throw (LoginException) firstProblem;
  389. }
  390. throw (LoginException) new LoginException(Messages.getString("auth.37")).initCause(firstProblem); //$NON-NLS-1$
  391. }
  392. }
  393. /**
  394. * <p>A class that servers as a wrapper for the CallbackHandler when we use
  395. * installed Configuration, but not a passed one. See API docs on the
  396. * LoginContext.</p>
  397. *
  398. * <p>Simply invokes the given handler with the given AccessControlContext.</p>
  399. */
  400. private class ContextedCallbackHandler implements CallbackHandler {
  401. private final CallbackHandler hiddenHandlerRef;
  402. ContextedCallbackHandler(CallbackHandler handler) {
  403. super();
  404. this.hiddenHandlerRef = handler;
  405. }
  406. public void handle(final Callback[] callbacks) throws IOException,
  407. UnsupportedCallbackException {
  408. try {
  409. AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
  410. public Void run() throws IOException, UnsupportedCallbackException {
  411. hiddenHandlerRef.handle(callbacks);
  412. return null;
  413. }
  414. }, userContext);
  415. } catch (PrivilegedActionException ex) {
  416. if (ex.getCause() instanceof UnsupportedCallbackException) {
  417. throw (UnsupportedCallbackException) ex.getCause();
  418. }
  419. throw (IOException) ex.getCause();
  420. }
  421. }
  422. }
  423. /**
  424. * A private class that stores an instantiated LoginModule.
  425. */
  426. private final class Module {
  427. // An initial info about the module to be used
  428. AppConfigurationEntry entry;
  429. // A mapping of LoginModuleControlFlag onto a simple int constant
  430. int flag;
  431. // The LoginModule itself
  432. LoginModule module;
  433. // A class of the module
  434. Class<?> klass;
  435. Module(AppConfigurationEntry entry) {
  436. this.entry = entry;
  437. LoginModuleControlFlag flg = entry.getControlFlag();
  438. if (flg == LoginModuleControlFlag.OPTIONAL) {
  439. flag = OPTIONAL;
  440. } else if (flg == LoginModuleControlFlag.REQUISITE) {
  441. flag = REQUISITE;
  442. } else if (flg == LoginModuleControlFlag.SUFFICIENT) {
  443. flag = SUFFICIENT;
  444. } else {
  445. flag = REQUIRED;
  446. //if(flg!=LoginModuleControlFlag.REQUIRED) throw new Error()
  447. }
  448. }
  449. int getFlag() {
  450. return flag;
  451. }
  452. /**
  453. * Loads class of the LoginModule, instantiates it and then calls
  454. * initialize().
  455. */
  456. void create(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState)
  457. throws LoginException {
  458. String klassName = entry.getLoginModuleName();
  459. if (klass == null) {
  460. try {
  461. klass = Class.forName(klassName, false, contextClassLoader);
  462. } catch (ClassNotFoundException ex) {
  463. throw (LoginException) new LoginException(Messages.getString(
  464. "auth.39", klassName)).initCause(ex); //$NON-NLS-1$
  465. }
  466. }
  467. if (module == null) {
  468. try {
  469. module = (LoginModule) klass.newInstance();
  470. } catch (IllegalAccessException ex) {
  471. throw (LoginException) new LoginException(Messages.getString(
  472. "auth.3A", klassName)) //$NON-NLS-1$
  473. .initCause(ex);
  474. } catch (InstantiationException ex) {
  475. throw (LoginException) new LoginException(Messages.getString(
  476. "auth.3A", klassName)) //$NON-NLS-1$
  477. .initCause(ex);
  478. }
  479. module.initialize(subject, callbackHandler, sharedState, entry.getOptions());
  480. }
  481. }
  482. }
  483. }