/******************************************************************************* * Copyright (c) 2015 IBH SYSTEMS GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBH SYSTEMS GmbH - initial API and implementation *******************************************************************************/ package org.eclipse.packagedrone.sec.service.admin; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.Set; import org.eclipse.packagedrone.sec.UserInformation; import org.eclipse.packagedrone.sec.service.LoginException; import org.eclipse.packagedrone.sec.service.UserService; import org.eclipse.packagedrone.sec.service.common.Users; import org.eclipse.scada.utils.ExceptionHelper; public class GodModeService implements UserService { private static final String PROP_BASE = "package.drone"; private static final String PROP_NOT_POSIX = PROP_BASE + ".admin.announce.file.notPosix"; private final String adminSalt; private final String adminTokenHash; private static final String NAME = System.getProperty ( PROP_BASE + ".admin.user", "admin" ); private static final Set<String> ROLES; private static final boolean ANNOUNCE_CONSOLE = !Boolean.getBoolean ( PROP_BASE + ".admin.disableAnnounce.console" ); private static final File ANNOUNCE_FILE; private static final boolean ANNOUNCE_FILE_POSIX = !Boolean.getBoolean ( PROP_NOT_POSIX ); private static final boolean ENABLED = !Boolean.getBoolean ( PROP_BASE + ".admin.disabled" ); private static final String EXTERNAL_ADMIN_TOKEN_HASH; private static final String EXTERNAL_ADMIN_TOKEN_SALT; static { final String externalAdminToken = System.getProperty ( PROP_BASE + ".admin.token", System.getenv ( "PACKAGE_DRONE_ADMIN_TOKEN" ) ); if ( externalAdminToken != null && !externalAdminToken.isEmpty () ) { EXTERNAL_ADMIN_TOKEN_SALT = Users.createToken ( 32 ); EXTERNAL_ADMIN_TOKEN_HASH = Users.hashIt ( EXTERNAL_ADMIN_TOKEN_SALT, externalAdminToken ); } else { EXTERNAL_ADMIN_TOKEN_HASH = EXTERNAL_ADMIN_TOKEN_SALT = null; } } static { // roles ROLES = Collections.unmodifiableSet ( new HashSet<> ( Arrays.asList ( System.getProperty ( PROP_BASE + ".admin.roles", "ADMIN, USER" ).split ( "\\s*,\\s*" ) ) ) ); // announce file final String file = System.getProperty ( PROP_BASE + ".admin.announce.file" ); final String userDir = System.getProperty ( "user.home" ); if ( file != null ) { ANNOUNCE_FILE = new File ( file ); } else if ( userDir != null ) { ANNOUNCE_FILE = new File ( new File ( userDir ), ".drone-admin-token" ); } else { ANNOUNCE_FILE = null; } } public GodModeService () { final String adminToken = Users.createToken ( 32 ); this.adminSalt = Users.createToken ( 32 ); this.adminTokenHash = Users.hashIt ( this.adminSalt, adminToken ); if ( ENABLED ) { announce ( adminToken ); } } @Override public UserInformation checkCredentials ( final String username, final String credentials, final boolean rememberMe ) throws LoginException { if ( !ENABLED ) { return null; } if ( !NAME.equals ( username ) ) { return null; } final String hashedPassword = Users.hashIt ( this.adminSalt, credentials ); // check generated token if ( hashedPassword.equals ( this.adminTokenHash ) ) { return getUserInformation (); } // check external token if ( EXTERNAL_ADMIN_TOKEN_HASH != null && Users.hashIt ( EXTERNAL_ADMIN_TOKEN_SALT, credentials ).equals ( EXTERNAL_ADMIN_TOKEN_HASH ) ) { return getUserInformation (); } return null; } private UserInformation getUserInformation () { return new UserInformation ( NAME, ROLES ); } @Override public UserInformation refresh ( final UserInformation user ) { return null; } private void announce ( final String adminToken ) { if ( ANNOUNCE_CONSOLE ) { System.out.println ( "=================== Admin>> ===================" ); System.out.println ( " User: " + NAME ); System.out.println ( " Password: " + adminToken ); System.out.println ( "=================== <<Admin ===================" ); } if ( ANNOUNCE_FILE != null ) { writeAnnounceFile ( adminToken ); } } /** * Write out the announce file <br> * Writing a file like this seems a little bit strange from a Java * perspective. However, there seems to be no other way to create a file * with a provided set of initial file attributes other than * {@link Files#newByteChannel(java.nio.file.Path, Set, FileAttribute...)}. * All other methods don't access file attributes. * * @param adminToken * The admin token to write out */ protected void writeAnnounceFile ( final String adminToken ) { final Path path = ANNOUNCE_FILE.toPath (); /* * Try to delete the file first. We don't care about the result * since the next operation will try to create a new file anyway. * * However by deleting it first, we try to ensure that the initial * file permissions are set. If the file exists these would not be * changed. */ ANNOUNCE_FILE.delete (); // posix final FileAttribute<?>[] attrs; if ( ANNOUNCE_FILE_POSIX ) { final Set<PosixFilePermission> perms = EnumSet.of ( PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE ); attrs = new FileAttribute<?>[] { PosixFilePermissions.asFileAttribute ( perms ) }; } else { attrs = new FileAttribute<?>[0]; } final Set<? extends OpenOption> options = EnumSet.of ( StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING ); try ( SeekableByteChannel sbc = Files.newByteChannel ( path, options, attrs ) ) { final StringWriter sw = new StringWriter (); final PrintWriter writer = new PrintWriter ( sw ); writer.format ( "user=%s%n", NAME ); writer.format ( "password=%s%n", adminToken ); writer.close (); final ByteBuffer data = ByteBuffer.wrap ( sw.toString ().getBytes ( StandardCharsets.UTF_8 ) ); while ( data.hasRemaining () ) { sbc.write ( data ); } } catch ( final UnsupportedOperationException e ) { System.err.format ( "WARNING: Failed to write out announce file with secured posix permissions. If you are on a non-posix platform (e.g. Windows), you might need to set the system property '%s' to 'true'%n", PROP_NOT_POSIX ); } catch ( final IOException e ) { System.err.println ( "WARNING: Unable to write announce file: " + ExceptionHelper.getMessage ( e ) ); } } @Override public boolean hasUserBase () { // we don't count as user base return false; } }