Completed
Push — master ( e4992c...6d0a35 )
by
unknown
10:42
created

OCI::initialize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Andreas Fischer <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Joas Schilling <[email protected]>
6
 * @author Jörn Friedrich Dreyer <[email protected]>
7
 * @author Manish Bisht <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Roeland Jago Douma <[email protected]>
10
 * @author Thomas Müller <[email protected]>
11
 * @author Victor Dubiniuk <[email protected]>
12
 *
13
 * @copyright Copyright (c) 2018, ownCloud GmbH
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
namespace OC\Setup;
30
31
class OCI extends AbstractDatabase {
32
	public $dbprettyname = 'Oracle';
33
34
	protected $dbtablespace;
35
36
	public function initialize($config) {
37
		parent::initialize($config);
38
		if (\array_key_exists('dbtablespace', $config)) {
39
			$this->dbtablespace = $config['dbtablespace'];
40
		} else {
41
			$this->dbtablespace = 'USERS';
42
		}
43
		// allow empty hostname for oracle
44
		$this->dbHost = $config['dbhost'];
45
46
		$this->config->setSystemValues([
47
			'dbhost'		=> $this->dbHost,
48
			'dbtablespace'	=> $this->dbtablespace,
49
		]);
50
	}
51
52
	public function validate($config) {
53
		$errors = [];
54 View Code Duplication
		if (empty($config['dbuser']) && empty($config['dbname'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
55
			$errors[] = $this->trans->t("%s enter the database username and name.", [$this->dbprettyname]);
56
		} elseif (empty($config['dbuser'])) {
57
			$errors[] = $this->trans->t("%s enter the database username.", [$this->dbprettyname]);
58
		} elseif (empty($config['dbname'])) {
59
			$errors[] = $this->trans->t("%s enter the database name.", [$this->dbprettyname]);
60
		}
61
		return $errors;
62
	}
63
64
	public function setupDatabase($username) {
65
		$e_host = \addslashes($this->dbHost);
66
		$e_dbname = \addslashes($this->dbName);
67
		//check if the database user has admin right
68 View Code Duplication
		if ($e_host == '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
69
			$easy_connect_string = $e_dbname; // use dbname as easy connect name
70
		} else {
71
			$easy_connect_string = '//'.$e_host.'/'.$e_dbname;
72
		}
73
		$this->logger->debug('connect string: ' . $easy_connect_string, ['app' => 'setup.oci']);
74
		$connection = @\oci_connect($this->dbUser, $this->dbPassword, $easy_connect_string);
75
		if (!$connection) {
76
			$errorMessage = $this->getLastError();
77
			if ($errorMessage) {
78
				throw new \OC\DatabaseSetupException($this->trans->t('Oracle connection could not be established'),
79
				$errorMessage.' Check environment: ORACLE_HOME='.\getenv('ORACLE_HOME')
80
							.' ORACLE_SID='.\getenv('ORACLE_SID')
81
							.' LD_LIBRARY_PATH='.\getenv('LD_LIBRARY_PATH')
82
							.' NLS_LANG='.\getenv('NLS_LANG')
83
							.' tnsnames.ora is '.(\is_readable(\getenv('ORACLE_HOME').'/network/admin/tnsnames.ora')?'':'not ').'readable');
84
			}
85
			throw new \OC\DatabaseSetupException($this->trans->t('Oracle username and/or password not valid'),
86
					'Check environment: ORACLE_HOME='.\getenv('ORACLE_HOME')
87
							.' ORACLE_SID='.\getenv('ORACLE_SID')
88
							.' LD_LIBRARY_PATH='.\getenv('LD_LIBRARY_PATH')
89
							.' NLS_LANG='.\getenv('NLS_LANG')
90
							.' tnsnames.ora is '.(\is_readable(\getenv('ORACLE_HOME').'/network/admin/tnsnames.ora')?'':'not ').'readable');
91
		}
92
		//check for roles creation rights in oracle
93
94
		$query="SELECT CASE WHEN (count(*) = 6) THEN 1 ELSE 0 END AS all_privileges
95
		        FROM user_role_privs a1
96
		        INNER JOIN role_sys_privs a2 ON a1.granted_role = a2.role
97
		        WHERE ( a2.privilege IN ('CREATE USER', 'ALTER USER') )
98
		            OR ( a2.privilege IN (
99
		                'CREATE SESSION',
100
		                'CREATE TABLE',
101
		                'CREATE SEQUENCE',
102
		                'CREATE TRIGGER'
103
		                ) AND a2.admin_option = 'YES')";
104
		$stmt = \oci_parse($connection, $query);
105 View Code Duplication
		if (!$stmt) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
106
			$entry = $this->trans->t('DB Error: "%s"', [$this->getLastError($connection)]) . '<br />';
107
			$entry .= $this->trans->t('Offending command was: "%s"', [$query]) . '<br />';
108
			$this->logger->warning($entry, ['app' => 'setup.oci']);
109
		}
110
		$result = \oci_execute($stmt);
111
		if ($result) {
112
			$row = \oci_fetch_row($stmt);
113
114
			if ($row[0] > 0) {
115
				//use the admin login data for the new database user
116
117
				//add prefix to the oracle user name to prevent collisions
118
				$this->dbUser='oc_'.$username;
119
				//create a new password so we don't need to store the admin config in the config file
120
				$this->dbPassword = \OC::$server->getSecureRandom()->generate(30, \OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_DIGITS);
121
122
				//oracle passwords are treated as identifiers:
123
				//  must start with alphanumeric char
124
				//  needs to be shortened to 30 bytes, as the two " needed to escape the identifier count towards the identifier length.
125
				$this->dbPassword=\substr($this->dbPassword, 0, 30);
126
127
				$this->createDBUser($connection);
128
			}
129
		}
130
131
		$this->config->setSystemValues([
132
			'dbuser'		=> $this->dbUser,
133
			'dbname'		=> $this->dbName,
134
			'dbpassword'	=> $this->dbPassword,
135
		]);
136
137
		//create the database not necessary, oracle implies user = schema
138
		//$this->createDatabase($this->dbname, $this->dbuser, $connection);
139
140
		//FIXME check tablespace exists: select * from user_tablespaces
141
142
		// the connection to dbname=oracle is not needed anymore
143
		\oci_close($connection);
144
145
		// connect to the oracle database (schema=$this->dbuser) an check if the schema needs to be filled
146
		$this->dbUser = $this->config->getSystemValue('dbuser');
147
		//$this->dbname = \OC_Config::getValue('dbname');
148
		$this->dbPassword = $this->config->getSystemValue('dbpassword');
149
150
		$e_host = \addslashes($this->dbHost);
151
		$e_dbname = \addslashes($this->dbName);
152
153 View Code Duplication
		if ($e_host == '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
154
			$easy_connect_string = $e_dbname; // use dbname as easy connect name
155
		} else {
156
			$easy_connect_string = '//'.$e_host.'/'.$e_dbname;
157
		}
158
		$connection = @\oci_connect($this->dbUser, $this->dbPassword, $easy_connect_string);
159
		if (!$connection) {
160
			throw new \OC\DatabaseSetupException($this->trans->t('Oracle username and/or password not valid'),
161
					$this->trans->t('You need to enter either an existing account or the administrator.'));
162
		}
163
		$query = "SELECT count(*) FROM user_tables WHERE table_name = :un";
164
		$stmt = \oci_parse($connection, $query);
165
		$un = $this->tablePrefix.'users';
166
		\oci_bind_by_name($stmt, ':un', $un);
167 View Code Duplication
		if (!$stmt) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
168
			$entry = $this->trans->t('DB Error: "%s"', [$this->getLastError($connection)]) . '<br />';
169
			$entry .= $this->trans->t('Offending command was: "%s"', [$query]) . '<br />';
170
			$this->logger->warning($entry, ['app' => 'setup.oci']);
171
		}
172
		$result = \oci_execute($stmt);
173
174
		if ($result) {
175
			$row = \oci_fetch_row($stmt);
176
		}
177
		if (!$result or $row[0]==0) {
0 ignored issues
show
Bug introduced by
The variable $row does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
178
			\OC_DB::createDbFromStructure($this->dbDefinitionFile);
179
		}
180
	}
181
182
	/**
183
	 * @param resource $connection
184
	 */
185
	private function createDBUser($connection) {
186
		$name = $this->dbUser;
187
		$password = $this->dbPassword;
188
		$query = "SELECT * FROM all_users WHERE USERNAME = :un";
189
		$stmt = \oci_parse($connection, $query);
190 View Code Duplication
		if (!$stmt) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
191
			$entry = $this->trans->t('DB Error: "%s"', [$this->getLastError($connection)]) . '<br />';
192
			$entry .= $this->trans->t('Offending command was: "%s"', [$query]) . '<br />';
193
			$this->logger->warning($entry, ['app' => 'setup.oci']);
194
		}
195
		\oci_bind_by_name($stmt, ':un', $name);
196
		$result = \oci_execute($stmt);
197 View Code Duplication
		if (!$result) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
198
			$entry = $this->trans->t('DB Error: "%s"', [$this->getLastError($connection)]) . '<br />';
199
			$entry .= $this->trans->t('Offending command was: "%s"', [$query]) . '<br />';
200
			$this->logger->warning($entry, ['app' => 'setup.oci']);
201
		}
202
203
		if (! \oci_fetch_row($stmt)) {
204
			//user does not exist, so let's create them :)
205
			//password must start with alphabetic character in oracle
206
			$query = 'CREATE USER '.$name.' IDENTIFIED BY "'.$password.'" DEFAULT TABLESPACE '.$this->dbtablespace;
207
			$stmt = \oci_parse($connection, $query);
208 View Code Duplication
			if (!$stmt) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
209
				$entry = $this->trans->t('DB Error: "%s"', [$this->getLastError($connection)]) . '<br />';
210
				$entry .= $this->trans->t('Offending command was: "%s"', [$query]) . '<br />';
211
				$this->logger->warning($entry, ['app' => 'setup.oci']);
212
			}
213
			//oci_bind_by_name($stmt, ':un', $name);
214
			$result = \oci_execute($stmt);
215 View Code Duplication
			if (!$result) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
216
				$entry = $this->trans->t('DB Error: "%s"', [$this->getLastError($connection)]) . '<br />';
217
				$entry .= $this->trans->t('Offending command was: "%s", name: %s, password: %s',
218
					[$query, $name, $password]) . '<br />';
219
				$this->logger->warning($entry, ['app' => 'setup.oci']);
220
			}
221
		} else { // change password of the existing role
222
			$query = "ALTER USER :un IDENTIFIED BY :pw";
223
			$stmt = \oci_parse($connection, $query);
224 View Code Duplication
			if (!$stmt) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
225
				$entry = $this->trans->t('DB Error: "%s"', [$this->getLastError($connection)]) . '<br />';
226
				$entry .= $this->trans->t('Offending command was: "%s"', [$query]) . '<br />';
227
				$this->logger->warning($entry, ['app' => 'setup.oci']);
228
			}
229
			\oci_bind_by_name($stmt, ':un', $name);
230
			\oci_bind_by_name($stmt, ':pw', $password);
231
			$result = \oci_execute($stmt);
232 View Code Duplication
			if (!$result) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
233
				$entry = $this->trans->t('DB Error: "%s"', [$this->getLastError($connection)]) . '<br />';
234
				$entry .= $this->trans->t('Offending command was: "%s"', [$query]) . '<br />';
235
				$this->logger->warning($entry, ['app' => 'setup.oci']);
236
			}
237
		}
238
		// grant necessary roles
239
		$query = 'GRANT CREATE SESSION, CREATE TABLE, CREATE SEQUENCE, CREATE TRIGGER TO '.$name;
240
		$stmt = \oci_parse($connection, $query);
241 View Code Duplication
		if (!$stmt) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
242
			$entry = $this->trans->t('DB Error: "%s"', [$this->getLastError($connection)]) . '<br />';
243
			$entry .= $this->trans->t('Offending command was: "%s"', [$query]) . '<br />';
244
			$this->logger->warning($entry, ['app' => 'setup.oci']);
245
		}
246
		$result = \oci_execute($stmt);
247 View Code Duplication
		if (!$result) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
248
			$entry = $this->trans->t('DB Error: "%s"', [$this->getLastError($connection)]) . '<br />';
249
			$entry .= $this->trans->t('Offending command was: "%s", name: %s, password: %s',
250
				[$query, $name, $password]) . '<br />';
251
			$this->logger->warning($entry, ['app' => 'setup.oci']);
252
		}
253
	}
254
255
	/**
256
	 * @param resource $connection
257
	 * @return string
258
	 */
259
	protected function getLastError($connection = null) {
260
		if ($connection) {
261
			$error = \oci_error($connection);
262
		} else {
263
			$error = \oci_error();
264
		}
265
		foreach (['message', 'code'] as $key) {
266
			if (isset($error[$key])) {
267
				return $error[$key];
268
			}
269
		}
270
		return '';
271
	}
272
}
273