001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.service;
020
021import java.io.BufferedReader;
022import java.io.File;
023import java.io.FileInputStream;
024import java.io.FileNotFoundException;
025import java.io.IOException;
026import java.io.InputStreamReader;
027import java.net.URI;
028import java.util.HashSet;
029import java.util.List;
030import java.util.Map;
031import java.util.ArrayList;
032import java.util.Set;
033
034import org.apache.commons.lang.StringUtils;
035import org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.fs.FileSystem;
037import org.apache.hadoop.fs.Path;
038import org.apache.hadoop.security.AccessControlException;
039import org.apache.oozie.BundleJobBean;
040import org.apache.oozie.CoordinatorJobBean;
041import org.apache.oozie.ErrorCode;
042import org.apache.oozie.WorkflowJobBean;
043import org.apache.oozie.client.XOozieClient;
044import org.apache.oozie.executor.jpa.BundleJobGetJPAExecutor;
045import org.apache.oozie.executor.jpa.CoordJobInfoGetJPAExecutor;
046import org.apache.oozie.executor.jpa.BundleJobInfoGetJPAExecutor;
047import org.apache.oozie.executor.jpa.WorkflowsJobGetJPAExecutor;
048import org.apache.oozie.executor.jpa.CoordJobGetJPAExecutor;
049import org.apache.oozie.executor.jpa.JPAExecutorException;
050import org.apache.oozie.executor.jpa.WorkflowJobQueryExecutor;
051import org.apache.oozie.executor.jpa.WorkflowJobQueryExecutor.WorkflowJobQuery;
052import org.apache.oozie.util.ConfigUtils;
053import org.apache.oozie.util.Instrumentation;
054import org.apache.oozie.util.XLog;
055
056/**
057 * The authorization service provides all authorization checks.
058 */
059public class AuthorizationService implements Service {
060
061    public static final String CONF_PREFIX = Service.CONF_PREFIX + "AuthorizationService.";
062
063    /**
064     * Configuration parameter to enable or disable Oozie admin role.
065     */
066    public static final String CONF_SECURITY_ENABLED = CONF_PREFIX + "security.enabled";
067
068    /**
069     * Configuration parameter to enable or disable Oozie admin role.
070     */
071    public static final String CONF_AUTHORIZATION_ENABLED = CONF_PREFIX + "authorization.enabled";
072
073    /**
074     * Configuration parameter to enable old behavior default group as ACL.
075     */
076    public static final String CONF_DEFAULT_GROUP_AS_ACL = CONF_PREFIX + "default.group.as.acl";
077
078    /**
079     * Configuration parameter to define admin groups, if NULL/empty the adminusers.txt file is used.
080     */
081    public static final String CONF_ADMIN_GROUPS = CONF_PREFIX + "admin.groups";
082
083    public static final String CONF_SYSTEM_INFO_AUTHORIZED_USERS = CONF_PREFIX + "system.info.authorized.users";
084
085
086    /**
087     * File that contains list of admin users for Oozie.
088     */
089    public static final String ADMIN_USERS_FILE = "adminusers.txt";
090
091    protected static final String INSTRUMENTATION_GROUP = "authorization";
092    protected static final String INSTR_FAILED_AUTH_COUNTER = "authorization.failed";
093
094    private Set<String> adminGroups;
095    private Set<String> adminUsers;
096    private Set<String> sysInfoAuthUsers;
097    private boolean authorizationEnabled;
098    private boolean useDefaultGroupAsAcl;
099    private boolean authorizedSystemInfo = false;
100    private final XLog log = XLog.getLog(getClass());
101    private Instrumentation instrumentation;
102
103    private String[] getTrimmedStrings(String str) {
104        if (null == str || "".equals(str.trim())) {
105            return new String[0];
106        }
107        return str.trim().split("\\s*,\\s*");
108    }
109
110    /**
111     * Initialize the service. <p> Reads the security related configuration. parameters - security enabled and list of
112     * super users.
113     *
114     * @param services services instance.
115     * @throws ServiceException thrown if the service could not be initialized.
116     */
117    public void init(Services services) throws ServiceException {
118        authorizationEnabled =
119            ConfigUtils.getWithDeprecatedCheck(services.getConf(), CONF_AUTHORIZATION_ENABLED,
120                                               CONF_SECURITY_ENABLED, false);
121        String systemInfoAuthUsers = ConfigurationService.get(CONF_SYSTEM_INFO_AUTHORIZED_USERS);
122        if (!StringUtils.isBlank(systemInfoAuthUsers)) {
123            authorizedSystemInfo = true;
124            sysInfoAuthUsers = new HashSet<>();
125            for (String user : getTrimmedStrings(systemInfoAuthUsers)) {
126                sysInfoAuthUsers.add(user);
127            }
128        }
129        if (authorizationEnabled) {
130            log.info("Oozie running with authorization enabled");
131            useDefaultGroupAsAcl = ConfigurationService.getBoolean(CONF_DEFAULT_GROUP_AS_ACL);
132            String[] str = getTrimmedStrings(Services.get().getConf().get(CONF_ADMIN_GROUPS));
133            if (str.length > 0) {
134                log.info("Admin users will be checked against the defined admin groups");
135                adminGroups = new HashSet<String>();
136                for (String s : str) {
137                    adminGroups.add(s.trim());
138                }
139            }
140            else {
141                log.info("Admin users will be checked against the 'adminusers.txt' file contents");
142                adminUsers = new HashSet<String>();
143                loadAdminUsers();
144            }
145        }
146        else {
147            log.warn("Oozie running with authorization disabled");
148        }
149        instrumentation = Services.get().get(InstrumentationService.class).get();
150    }
151
152    /**
153     * Return if security is enabled or not.
154     *
155     * @return if security is enabled or not.
156     */
157    @Deprecated
158    public boolean isSecurityEnabled() {
159        return authorizationEnabled;
160    }
161
162    public boolean useDefaultGroupAsAcl() {
163        return useDefaultGroupAsAcl;
164    }
165
166    /**
167     * Return if security is enabled or not.
168     *
169     * @return if security is enabled or not.
170     */
171    public boolean isAuthorizationEnabled() {
172        return isSecurityEnabled();
173    }
174
175    /**
176     * Load the list of admin users from {@link AuthorizationService#ADMIN_USERS_FILE} </p>
177     *
178     * @throws ServiceException if the admin user list could not be loaded.
179     */
180    private void loadAdminUsers() throws ServiceException {
181        String configDir = Services.get().get(ConfigurationService.class).getConfigDir();
182        if (configDir != null) {
183            File file = new File(configDir, ADMIN_USERS_FILE);
184            if (file.exists()) {
185                try {
186                    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
187                    try {
188                        String line = br.readLine();
189                        while (line != null) {
190                            line = line.trim();
191                            if (line.length() > 0 && !line.startsWith("#")) {
192                                adminUsers.add(line);
193                            }
194                            line = br.readLine();
195                        }
196                    }
197                    catch (IOException ex) {
198                        throw new ServiceException(ErrorCode.E0160, file.getAbsolutePath(), ex);
199                    }
200                }
201                catch (FileNotFoundException ex) {
202                    throw new ServiceException(ErrorCode.E0160, file.getAbsolutePath(), ex);
203                }
204            }
205            else {
206                log.warn("Admin users file not available in config dir [{0}], running without admin users", configDir);
207            }
208        }
209        else {
210            log.warn("Reading configuration from classpath, running without admin users");
211        }
212    }
213
214    /**
215     * Destroy the service. <p> This implementation does a NOP.
216     */
217    public void destroy() {
218    }
219
220    /**
221     * Return the public interface of the service.
222     *
223     * @return {@link AuthorizationService}.
224     */
225    public Class<? extends Service> getInterface() {
226        return AuthorizationService.class;
227    }
228
229    /**
230     * Check if the user belongs to the group or not.
231     *
232     * @param user user name.
233     * @param group group name.
234     * @return if the user belongs to the group or not.
235     * @throws AuthorizationException thrown if the authorization query can not be performed.
236     */
237    protected boolean isUserInGroup(String user, String group) throws AuthorizationException {
238        GroupsService groupsService = Services.get().get(GroupsService.class);
239        try {
240            return groupsService.getGroups(user).contains(group);
241        }
242        catch (IOException ex) {
243            throw new AuthorizationException(ErrorCode.E0501, ex.getMessage(), ex);
244        }
245    }
246
247    /**
248     * Check if the user belongs to the group or not. <p> <p> Subclasses should override the {@link #isUserInGroup}
249     * method.
250     *
251     * @param user user name.
252     * @param group group name.
253     * @throws AuthorizationException thrown if the user is not authorized for the group or if the authorization query
254     * can not be performed.
255     */
256    public void authorizeForGroup(String user, String group) throws AuthorizationException {
257        if (authorizationEnabled && !isUserInGroup(user, group)) {
258            throw new AuthorizationException(ErrorCode.E0502, user, group);
259        }
260    }
261
262    /**
263     * Return the default group to which the user belongs. <p> This implementation always returns 'users'.
264     *
265     * @param user user name.
266     * @return default group of user.
267     * @throws AuthorizationException thrown if the default group con not be retrieved.
268     */
269    public String getDefaultGroup(String user) throws AuthorizationException {
270        try {
271            return Services.get().get(GroupsService.class).getGroups(user).get(0);
272        }
273        catch (IOException ex) {
274            throw new AuthorizationException(ErrorCode.E0501, ex.getMessage(), ex);
275        }
276    }
277
278    /**
279     * Check if the user has admin privileges. <p> If admin is disabled it returns always <code>true</code>. <p> If
280     * admin is enabled it returns <code>true</code> if the user is in the <code>adminusers.txt</code> file.
281     *
282     * @param user user name.
283     * @return if the user has admin privileges or not.
284     */
285    protected boolean isAdmin(String user) {
286        boolean admin = false;
287        if (adminUsers != null) {
288            admin = adminUsers.contains(user);
289        }
290        else {
291            for (String adminGroup : adminGroups) {
292                try {
293                    admin = isUserInGroup(user, adminGroup);
294                    if (admin) {
295                        break;
296                    }
297                }
298                catch (AuthorizationException ex) {
299                    log.warn("Admin check failed, " + ex.toString(), ex);
300                    break;
301                }
302            }
303        }
304        return admin;
305    }
306
307    /**
308     * Check if the user is authorized to access system information.
309     *
310     * @param user user name.
311     * @param proxyUser proxy user name.
312     * @throws AuthorizationException thrown if user does not have admin priviledges.
313     */
314    public void authorizeForSystemInfo(String user, String proxyUser) throws AuthorizationException {
315        if (authorizationEnabled && authorizedSystemInfo && !(sysInfoAuthUsers.contains(user) || sysInfoAuthUsers
316                .contains(proxyUser) || isAdmin(user) || isAdmin(proxyUser))) {
317            incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
318            throw new AuthorizationException(ErrorCode.E0503, user);
319        }
320    }
321
322    /**
323     * Check if the user has admin privileges. <p> Subclasses should override the {@link #isUserInGroup} method.
324     *
325     * @param user user name.
326     * @param write indicates if the check is for read or write admin tasks (in this implementation this is ignored)
327     * @throws AuthorizationException thrown if user does not have admin privileges.
328     */
329    public void authorizeForAdmin(String user, boolean write) throws AuthorizationException {
330        if (authorizationEnabled && write && !isAdmin(user)) {
331            incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
332            throw new AuthorizationException(ErrorCode.E0503, user);
333        }
334    }
335
336    /**
337     * Check if the user+group is authorized to use the specified application. <p> The check is done by checking the
338     * file system permissions on the workflow application.
339     *
340     * @param user user name.
341     * @param group group name.
342     * @param appPath application path.
343     * @throws AuthorizationException thrown if the user is not authorized for the app.
344     */
345    public void authorizeForApp(String user, String group, String appPath, Configuration jobConf)
346            throws AuthorizationException {
347        try {
348            HadoopAccessorService has = Services.get().get(HadoopAccessorService.class);
349            URI uri = new Path(appPath).toUri();
350            Configuration fsConf = has.createConfiguration(uri.getAuthority());
351            FileSystem fs = has.createFileSystem(user, uri, fsConf);
352
353            Path path = new Path(appPath);
354            try {
355                if (!fs.exists(path)) {
356                    incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
357                    throw new AuthorizationException(ErrorCode.E0504, appPath);
358                }
359                Path wfXml = new Path(path, "workflow.xml");
360                if (!fs.exists(wfXml)) {
361                    incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
362                    throw new AuthorizationException(ErrorCode.E0505, appPath);
363                }
364                if (!fs.isFile(wfXml)) {
365                    incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
366                    throw new AuthorizationException(ErrorCode.E0506, appPath);
367                }
368                fs.open(wfXml).close();
369            }
370            catch (AccessControlException ex) {
371                incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
372                throw new AuthorizationException(ErrorCode.E0507, appPath, ex.getMessage(), ex);
373            }
374        }
375        catch (IOException ex) {
376            incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
377            throw new AuthorizationException(ErrorCode.E0501, ex.getMessage(), ex);
378        }
379        catch (HadoopAccessorException e) {
380            throw new AuthorizationException(e);
381        }
382    }
383
384    /**
385     * Check if the user+group is authorized to use the specified application. <p> The check is done by checking the
386     * file system permissions on the workflow application.
387     *
388     * @param user user name.
389     * @param group group name.
390     * @param appPath application path.
391     * @param fileName workflow or coordinator.xml
392     * @param conf
393     * @throws AuthorizationException thrown if the user is not authorized for the app.
394     */
395    public void authorizeForApp(String user, String group, String appPath, String fileName, Configuration conf)
396            throws AuthorizationException {
397        try {
398            HadoopAccessorService has = Services.get().get(HadoopAccessorService.class);
399            URI uri = new Path(appPath).toUri();
400            Configuration fsConf = has.createConfiguration(uri.getAuthority());
401            FileSystem fs = has.createFileSystem(user, uri, fsConf);
402
403            Path path = new Path(appPath);
404            try {
405                if (!fs.exists(path)) {
406                    incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
407                    throw new AuthorizationException(ErrorCode.E0504, appPath);
408                }
409                if (conf.get(XOozieClient.IS_PROXY_SUBMISSION) == null) { // Only further check existence of job definition
410                    //files for non proxy submission jobs;
411                    if (!fs.isFile(path)) {
412                        Path appXml = new Path(path, fileName);
413                        if (!fs.exists(appXml)) {
414                            incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
415                            throw new AuthorizationException(ErrorCode.E0505, appPath);
416                        }
417                        if (!fs.isFile(appXml)) {
418                            incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
419                            throw new AuthorizationException(ErrorCode.E0506, appPath);
420                        }
421                        fs.open(appXml).close();
422                    }
423                }
424            }
425            catch (AccessControlException ex) {
426                incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
427                throw new AuthorizationException(ErrorCode.E0507, appPath, ex.getMessage(), ex);
428            }
429        }
430        catch (IOException ex) {
431            incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
432            throw new AuthorizationException(ErrorCode.E0501, ex.getMessage(), ex);
433        }
434        catch (HadoopAccessorException e) {
435            throw new AuthorizationException(e);
436        }
437    }
438
439    private boolean isUserInAcl(String user, String aclStr) throws IOException {
440        boolean userInAcl = false;
441        if (aclStr != null && aclStr.trim().length() > 0) {
442            GroupsService groupsService = Services.get().get(GroupsService.class);
443            String[] acl = aclStr.split(",");
444            for (int i = 0; !userInAcl && i < acl.length; i++) {
445                String aclItem = acl[i].trim();
446                userInAcl = aclItem.equals(user) || groupsService.getGroups(user).contains(aclItem);
447            }
448        }
449        return userInAcl;
450    }
451
452    /**
453     * Check if the user+group is authorized to operate on the specified job. <p> Checks if the user is a super-user or
454     * the one who started the job. <p> Read operations are allowed to all users.
455     *
456     * @param user user name.
457     * @param jobId job id.
458     * @param write indicates if the check is for read or write job tasks.
459     * @throws AuthorizationException thrown if the user is not authorized for the job.
460     */
461    public void authorizeForJob(String user, String jobId, boolean write) throws AuthorizationException {
462        if (authorizationEnabled && write && !isAdmin(user)) {
463            try {
464                // handle workflow jobs
465                if (jobId.endsWith("-W")) {
466                    WorkflowJobBean jobBean = null;
467                    JPAService jpaService = Services.get().get(JPAService.class);
468                    if (jpaService != null) {
469                        try {
470                            jobBean = WorkflowJobQueryExecutor.getInstance().get(WorkflowJobQuery.GET_WORKFLOW_USER_GROUP, jobId);
471                        }
472                        catch (JPAExecutorException je) {
473                            throw new AuthorizationException(je);
474                        }
475                    }
476                    else {
477                        throw new AuthorizationException(ErrorCode.E0610);
478                    }
479                    if (jobBean != null && !jobBean.getUser().equals(user)) {
480                        if (!isUserInAcl(user, jobBean.getGroup())) {
481                            incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
482                            throw new AuthorizationException(ErrorCode.E0508, user, jobId);
483                        }
484                    }
485                }
486                // handle bundle jobs
487                else if (jobId.endsWith("-B")){
488                    BundleJobBean jobBean = null;
489                    JPAService jpaService = Services.get().get(JPAService.class);
490                    if (jpaService != null) {
491                        try {
492                            jobBean = jpaService.execute(new BundleJobGetJPAExecutor(jobId));
493                        }
494                        catch (JPAExecutorException je) {
495                            throw new AuthorizationException(je);
496                        }
497                    }
498                    else {
499                        throw new AuthorizationException(ErrorCode.E0610);
500                    }
501                    if (jobBean != null && !jobBean.getUser().equals(user)) {
502                        if (!isUserInAcl(user, jobBean.getGroup())) {
503                            incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
504                            throw new AuthorizationException(ErrorCode.E0509, user, jobId);
505                        }
506                    }
507                }
508                // handle coordinator jobs
509                else {
510                    CoordinatorJobBean jobBean = null;
511                    JPAService jpaService = Services.get().get(JPAService.class);
512                    if (jpaService != null) {
513                        try {
514                            jobBean = jpaService.execute(new CoordJobGetJPAExecutor(jobId));
515                        }
516                        catch (JPAExecutorException je) {
517                            throw new AuthorizationException(je);
518                        }
519                    }
520                    else {
521                        throw new AuthorizationException(ErrorCode.E0610);
522                    }
523                    if (jobBean != null && !jobBean.getUser().equals(user)) {
524                        if (!isUserInAcl(user, jobBean.getGroup())) {
525                            incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
526                            throw new AuthorizationException(ErrorCode.E0509, user, jobId);
527                        }
528                    }
529                }
530            }
531            catch (IOException ex) {
532                throw new AuthorizationException(ErrorCode.E0501, ex.getMessage(), ex);
533            }
534        }
535    }
536
537    /**
538     * Check if the user+group is authorized to operate on the specified jobs. <p> Checks if the user is a super-user or
539     * the one who started the jobs. <p> Read operations are allowed to all users.
540     *
541     * @param user user name.
542     * @param filter filter used to select jobs
543     * @param start starting index of the jobs in DB
544     * @param len maximum amount of jobs to select
545     * @param write indicates if the check is for read or write job tasks.
546     * @throws AuthorizationException thrown if the user is not authorized for the job.
547     */
548    public void authorizeForJobs(String user, Map<String, List<String>> filter, String jobType,
549                                 int start, int len, boolean write) throws AuthorizationException {
550        if (authorizationEnabled && write && !isAdmin(user)) {
551            try {
552                // handle workflow jobs
553                if (jobType.equals("wf")) {
554                    List<WorkflowJobBean> jobBeans = new ArrayList<WorkflowJobBean>();
555                    JPAService jpaService = Services.get().get(JPAService.class);
556                    if (jpaService != null) {
557                        try {
558                            jobBeans = jpaService.execute(new WorkflowsJobGetJPAExecutor(
559                                    filter, start, len)).getWorkflows();
560                        }
561                        catch (JPAExecutorException je) {
562                            throw new AuthorizationException(je);
563                        }
564                    }
565                    else {
566                        throw new AuthorizationException(ErrorCode.E0610);
567                    }
568                    for (WorkflowJobBean jobBean : jobBeans) {
569                        if (jobBean != null && !jobBean.getUser().equals(user)) {
570                            if (!isUserInAcl(user, jobBean.getGroup())) {
571                                    incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
572                                throw new AuthorizationException(ErrorCode.E0508, user, jobBean.getId());
573                            }
574                        }
575                    }
576                }
577                // handle bundle jobs
578                else if (jobType.equals("bundle")) {
579                    List<BundleJobBean> jobBeans = new ArrayList<BundleJobBean>();
580                    JPAService jpaService = Services.get().get(JPAService.class);
581                    if (jpaService != null) {
582                        try {
583                            jobBeans = jpaService.execute(new BundleJobInfoGetJPAExecutor(
584                                    filter, start, len)).getBundleJobs();
585                        }
586                        catch (JPAExecutorException je) {
587                            throw new AuthorizationException(je);
588                        }
589                    }
590                    else {
591                        throw new AuthorizationException(ErrorCode.E0610);
592                    }
593                    for (BundleJobBean jobBean : jobBeans){
594                        if (jobBean != null && !jobBean.getUser().equals(user)) {
595                            if (!isUserInAcl(user, jobBean.getGroup())) {
596                                incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
597                                throw new AuthorizationException(ErrorCode.E0509, user, jobBean.getId());
598                            }
599                        }
600                    }
601                }
602                // handle coordinator jobs
603                else {
604                    List<CoordinatorJobBean> jobBeans = new ArrayList<CoordinatorJobBean>();
605                    JPAService jpaService = Services.get().get(JPAService.class);
606                    if (jpaService != null) {
607                        try {
608                            jobBeans = jpaService.execute(new CoordJobInfoGetJPAExecutor(
609                                    filter, start, len)).getCoordJobs();
610                        }
611                        catch (JPAExecutorException je) {
612                            throw new AuthorizationException(je);
613                        }
614                    }
615                    else {
616                        throw new AuthorizationException(ErrorCode.E0610);
617                    }
618                    for (CoordinatorJobBean jobBean : jobBeans) {
619                        if (jobBean != null && !jobBean.getUser().equals(user)) {
620                            if (!isUserInAcl(user, jobBean.getGroup())) {
621                                incrCounter(INSTR_FAILED_AUTH_COUNTER, 1);
622                                throw new AuthorizationException(ErrorCode.E0509, user, jobBean.getId());
623                            }
624                        }
625                    }
626                }
627            }
628            catch (IOException ex) {
629                throw new AuthorizationException(ErrorCode.E0501, ex.getMessage(), ex);
630            }
631        }
632    }
633    /**
634     * Convenience method for instrumentation counters.
635     *
636     * @param name counter name.
637     * @param count count to increment the counter.
638     */
639    private void incrCounter(String name, int count) {
640        if (instrumentation != null) {
641            instrumentation.incr(INSTRUMENTATION_GROUP, name, count);
642        }
643    }
644
645    public boolean isAuthorizedSystemInfo() {
646        return authorizedSystemInfo;
647    }
648}