Skip to content
Snippets Groups Projects
Unverified Commit 0951c7fc authored by Martin Weise's avatar Martin Weise
Browse files

Completely got rid of the admin credentials, only working with hashing

parent a7026c4e
No related branches found
No related tags found
2 merge requests!163Relase 1.3.0,!155Added readme to authentication service and added eureka service
Showing
with 204 additions and 36 deletions
package at.tuwien.entities.auth; package at.tuwien.entities.auth;
import lombok.*; import lombok.*;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*; import javax.persistence.*;
...@@ -16,7 +17,9 @@ public class Realm { ...@@ -16,7 +17,9 @@ public class Realm {
@Id @Id
@EqualsAndHashCode.Include @EqualsAndHashCode.Include
@Column(nullable = false) @GeneratedValue(generator = "realm-uuid")
@GenericGenerator(name = "realm-uuid", strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = "ID", nullable = false, columnDefinition = "VARCHAR(36)")
private String id; private String id;
@Column(nullable = false) @Column(nullable = false)
...@@ -25,7 +28,4 @@ public class Realm { ...@@ -25,7 +28,4 @@ public class Realm {
@Column(nullable = false) @Column(nullable = false)
private String name; private String name;
@Column(nullable = false)
private String sslRequired;
} }
package at.tuwien.entities.user;
import lombok.*;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
@Data
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EntityListeners(AuditingEntityListener.class)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Table(name = "credential")
public class Credential {
@Id
@EqualsAndHashCode.Include
@GeneratedValue(generator = "credential-uuid")
@GenericGenerator(name = "credential-uuid", strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = "ID", nullable = false, columnDefinition = "VARCHAR(36)")
private String id;
@Column(nullable = false)
private String type;
@Column(name = "user_id", nullable = false)
private String userId;
@Column(nullable = false)
private Long createdDate;
@Column(nullable = false, columnDefinition = "LONGTEXT")
private String secretData;
@Column(nullable = false, columnDefinition = "LONGTEXT")
private String credentialData;
@Column(nullable = false)
private Integer priority;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumns({
@JoinColumn(name = "user_id", referencedColumnName = "id", insertable = false, updatable = false)
})
private User user;
}
...@@ -4,6 +4,7 @@ import at.tuwien.entities.container.Container; ...@@ -4,6 +4,7 @@ import at.tuwien.entities.container.Container;
import at.tuwien.entities.database.Database; import at.tuwien.entities.database.Database;
import at.tuwien.entities.identifier.Identifier; import at.tuwien.entities.identifier.Identifier;
import lombok.*; import lombok.*;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Immutable;
import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.springframework.data.jpa.domain.support.AuditingEntityListener;
...@@ -16,7 +17,6 @@ import java.util.List; ...@@ -16,7 +17,6 @@ import java.util.List;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@ToString @ToString
@Immutable
@EntityListeners(AuditingEntityListener.class) @EntityListeners(AuditingEntityListener.class)
@EqualsAndHashCode(onlyExplicitlyIncluded = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Table(name = "user_entity") @Table(name = "user_entity")
...@@ -24,7 +24,9 @@ public class User { ...@@ -24,7 +24,9 @@ public class User {
@Id @Id
@EqualsAndHashCode.Include @EqualsAndHashCode.Include
@Column(nullable = false) @GeneratedValue(generator = "user-uuid")
@GenericGenerator(name = "user-uuid", strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = "ID", nullable = false, columnDefinition = "VARCHAR(36)")
private String id; private String id;
@Column(unique = true, nullable = false) @Column(unique = true, nullable = false)
...@@ -45,11 +47,21 @@ public class User { ...@@ -45,11 +47,21 @@ public class User {
@Column(nullable = false) @Column(nullable = false)
private Boolean emailVerified; private Boolean emailVerified;
@Column(nullable = false)
private Boolean enabled;
@Column
private Long createdTimestamp;
@Transient @Transient
@ToString.Exclude @ToString.Exclude
@Column(nullable = false) @Column(nullable = false)
private String databasePassword; private String databasePassword;
@Column(nullable = false)
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private List<Credential> credentials;
@Transient @Transient
@ToString.Exclude @ToString.Exclude
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "owner") @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "owner")
......
const axios = require('axios/dist/browser/axios.cjs')
export function determineDataTypes (token, filepath) {
const payload = {
filepath
}
return axios.post('/api/analyse/determinedt', payload, {
headers: {
Authorization: `Bearer ${token}`
}
})
}
...@@ -4,6 +4,10 @@ export function listContainers (limit) { ...@@ -4,6 +4,10 @@ export function listContainers (limit) {
return axios.get(`/api/container?limit=${limit}`) return axios.get(`/api/container?limit=${limit}`)
} }
export function findContainer (containerId) {
return axios.get(`/api/container${containerId}`)
}
export function createContainer (token, payload) { export function createContainer (token, payload) {
return axios.post('/api/container/', payload, { return axios.post('/api/container/', payload, {
headers: { headers: {
......
...@@ -23,6 +23,17 @@ export function modifyVisibility (token, containerId, databaseId, isPublic) { ...@@ -23,6 +23,17 @@ export function modifyVisibility (token, containerId, databaseId, isPublic) {
}) })
} }
export function modifyOwnership (token, containerId, databaseId, username) {
const payload = {
username
}
return axios.put(`/api/container/${containerId}/database/${databaseId}/transfer`, payload, {
headers: {
Authorization: `Bearer ${token}`
}
})
}
export function findDatabase (token, containerId, databaseId) { export function findDatabase (token, containerId, databaseId) {
return axios.get(`/api/container/${containerId}/database/${databaseId}`, { return axios.get(`/api/container/${containerId}/database/${databaseId}`, {
headers: { headers: {
......
...@@ -15,3 +15,11 @@ export function createTable (token, containerId, databaseId, payload) { ...@@ -15,3 +15,11 @@ export function createTable (token, containerId, databaseId, payload) {
} }
}) })
} }
export function dataImport (token, containerId, databaseId, tableId, payload) {
return axios.post(`/api/container/${containerId}/database/${databaseId}/table/${tableId}/data/import`, payload, {
headers: {
Authorization: `Bearer ${token}`
}
})
}
...@@ -51,10 +51,16 @@ export function tokenToUser (token) { ...@@ -51,10 +51,16 @@ export function tokenToUser (token) {
export function tokenToExp (token) { export function tokenToExp (token) {
const data = jwt_decode(token) const data = jwt_decode(token)
if (!data) {
return new Date()
}
return new Date(data.exp * 1000) return new Date(data.exp * 1000)
} }
export function tokenToRoles (token) { export function tokenToRoles (token) {
const data = jwt_decode(token) const data = jwt_decode(token)
return data.realm_access.roles if (!data) {
return []
}
return data.realm_access.roles || []
} }
...@@ -98,20 +98,20 @@ export default { ...@@ -98,20 +98,20 @@ export default {
}, },
methods: { methods: {
formatOwner (container) { formatOwner (container) {
if (!('database' in container)) { if (!('database' in container) || !container.database) {
return formatUser(container.creator) return formatUser(container.creator)
} }
return formatUser(container.database?.owner) return formatUser(container.database?.owner)
}, },
formatCreators (container) { formatCreators (container) {
const creators = formatCreators(container) const creators = formatCreators(container)
return creators || this.formatCreator(container.creator) return creators || this.formatUser(container.creator)
}, },
canInit (container) { canInit (container) {
if (!this.user) { if (!this.user) {
return false return false
} }
if (container.creator.sub !== this.user.sub) { if (container.creator.username !== this.user.username) {
return false return false
} }
return !container.database return !container.database
......
const config = {} const config = {}
config.api = process.env.API || 'http://localhost:9095' config.api = process.env.API || 'https://gateway-service:9095'
config.search = process.env.SEARCH || 'http://localhost:9200' config.search = process.env.SEARCH || 'http://localhost:9200'
config.sandbox = process.env.SANDBOX || false config.sandbox = process.env.SANDBOX || false
config.title = process.env.TITLE || 'Database Repository' config.title = process.env.TITLE || 'Database Repository'
......
...@@ -252,7 +252,7 @@ export default { ...@@ -252,7 +252,7 @@ export default {
this.$router.push({ path: '/login', query: redirect ? { redirect: this.$router.currentRoute.path } : {} }) this.$router.push({ path: '/login', query: redirect ? { redirect: this.$router.currentRoute.path } : {} })
}, },
logout (message) { logout (message) {
if (message) { if (typeof message === 'string') {
this.$toast.warning(message) this.$toast.warning(message)
} }
this.$store.commit('SET_TOKEN', null) this.$store.commit('SET_TOKEN', null)
......
...@@ -84,7 +84,7 @@ export default { ...@@ -84,7 +84,7 @@ export default {
proxy: { proxy: {
'/api': api, '/api': api,
'/pid': { '/pid': {
target: process.env.API + '/api' || 'https://localhost:9095/api', target: api + '/api',
changeOrigin: true, changeOrigin: true,
pathRewrite: { pathRewrite: {
'^/pid': '/pid' '^/pid': '/pid'
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "nuxt --port 3001", "dev": "NODE_OPTIONS=--use-openssl-ca NODE_EXTRA_CA_CERTS=./root.crt nuxt --port 3001",
"docker": "nuxt > /dev/null", "docker": "nuxt > /dev/null",
"build": "nuxt build", "build": "nuxt build",
"start": "nuxt start", "start": "nuxt start",
......
...@@ -64,6 +64,8 @@ ...@@ -64,6 +64,8 @@
id="owner" id="owner"
v-model="modifyOwner.username" v-model="modifyOwner.username"
:items="users" :items="users"
item-text="username"
item-value="username"
label="Owner" label="Owner"
name="owner" /> name="owner" />
</v-col> </v-col>
...@@ -72,8 +74,8 @@ ...@@ -72,8 +74,8 @@
small small
color="warning" color="warning"
class="black--text" class="black--text"
@click="updateDatabaseVisibility"> @click="updateDatabaseOwner">
Modify Visibility Modify Ownership
</v-btn> </v-btn>
</v-card-text> </v-card-text>
</v-card> </v-card>
...@@ -91,7 +93,7 @@ ...@@ -91,7 +93,7 @@
<script> <script>
import DBToolbar from '@/components/DBToolbar' import DBToolbar from '@/components/DBToolbar'
import EditAccess from '@/components/dialogs/EditAccess' import EditAccess from '@/components/dialogs/EditAccess'
import { modifyVisibility } from '@/api/database' import { modifyVisibility, modifyOwnership } from '@/api/database'
export default { export default {
components: { components: {
...@@ -186,6 +188,7 @@ export default { ...@@ -186,6 +188,7 @@ export default {
return return
} }
this.modifyVisibility.is_public = this.database.is_public this.modifyVisibility.is_public = this.database.is_public
this.modifyOwner.username = this.database.owner.username
} }
}, },
mounted () { mounted () {
...@@ -194,6 +197,7 @@ export default { ...@@ -194,6 +197,7 @@ export default {
return return
} }
this.modifyVisibility.is_public = this.database.is_public this.modifyVisibility.is_public = this.database.is_public
this.modifyOwner.username = this.database.owner.username
}, },
methods: { methods: {
closeDialog (event) { closeDialog (event) {
...@@ -215,6 +219,17 @@ export default { ...@@ -215,6 +219,17 @@ export default {
} }
this.loading = false this.loading = false
}, },
async updateDatabaseOwner () {
try {
this.loading = true
await modifyOwnership(this.token, this.$route.params.container_id, this.$route.params.database_id, this.modifyOwner.username)
this.$toast.success('Successfully updated the database owner')
} catch (error) {
console.error('Failed to update database owner', error)
this.$toast.error('Failed to update database owner')
}
this.loading = false
},
giveAccess () { giveAccess () {
this.username = null this.username = null
this.editAccessDialog = true this.editAccessDialog = true
......
...@@ -187,7 +187,9 @@ ...@@ -187,7 +187,9 @@
<script> <script>
import TableSchema from '@/components/TableSchema' import TableSchema from '@/components/TableSchema'
import { notEmpty, isNonNegativeInteger, isResearcher } from '@/utils' import { notEmpty, isNonNegativeInteger, isResearcher } from '@/utils'
import { listTables } from '@/api/table' import { findContainer } from '@/api/container'
import { listTables, createTable, dataImport } from '@/api/table'
import { determineDataTypes } from '@/api/analyse'
export default { export default {
name: 'TableFromCSV', name: 'TableFromCSV',
...@@ -317,7 +319,6 @@ export default { ...@@ -317,7 +319,6 @@ export default {
this.file = res.data this.file = res.data
} catch (err) { } catch (err) {
console.error('Failed to upload .csv data', err) console.error('Failed to upload .csv data', err)
console.debug('failed to upload .csv data, does the .csv contain a header line?')
this.$toast.error('Could not upload data') this.$toast.error('Could not upload data')
} }
this.loading = false this.loading = false
...@@ -325,8 +326,7 @@ export default { ...@@ -325,8 +326,7 @@ export default {
async analyse () { async analyse () {
this.loading = true this.loading = true
try { try {
const payload = { filepath: `/tmp/${this.file.filename}` } const res = await determineDataTypes(this.token, `/tmp/${this.file.filename}`)
const res = await this.$axios.post('/api/analyse/determinedt', payload, this.config)
const { columns } = res.data const { columns } = res.data
console.log('data analyse result', columns) console.log('data analyse result', columns)
this.tableCreate.columns = Object.entries(columns) this.tableCreate.columns = Object.entries(columns)
...@@ -334,12 +334,8 @@ export default { ...@@ -334,12 +334,8 @@ export default {
return { return {
name: key, name: key,
type: val, type: val,
check_expression: null,
foreign_key: null,
references: null,
null_allowed: true, null_allowed: true,
primary_key: false, primary_key: false,
unique: false,
enum_values: [] enum_values: []
} }
}) })
...@@ -382,11 +378,10 @@ export default { ...@@ -382,11 +378,10 @@ export default {
column.unique = true column.unique = true
}, },
async loadDateFormats () { async loadDateFormats () {
const getUrl = `/api/container/${this.$route.params.container_id}`
let getResult let getResult
try { try {
this.loading = true this.loading = true
getResult = await this.$axios.get(getUrl, this.config) getResult = await findContainer(this.$route.params.container_id)
this.dateFormats = getResult.data.image.date_formats this.dateFormats = getResult.data.image.date_formats
console.debug('retrieve image date formats', this.dateFormats) console.debug('retrieve image date formats', this.dateFormats)
this.loading = false this.loading = false
...@@ -415,11 +410,10 @@ export default { ...@@ -415,11 +410,10 @@ export default {
// bail out if there is a problem with one of the columns // bail out if there is a problem with one of the columns
if (!validColumns.every(Boolean)) { return } if (!validColumns.every(Boolean)) { return }
const createUrl = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table`
let createResult let createResult
try { try {
this.loading = true this.loading = true
createResult = await this.$axios.post(createUrl, this.tableCreate, this.config) createResult = await createTable(this.token, this.$route.params.container_id, this.$route.params.database_id, this.tableCreate)
this.newTableId = createResult.data.id this.newTableId = createResult.data.id
console.debug('created table', createResult.data) console.debug('created table', createResult.data)
} catch (err) { } catch (err) {
...@@ -433,10 +427,9 @@ export default { ...@@ -433,10 +427,9 @@ export default {
console.error('create table failed', err) console.error('create table failed', err)
return return
} }
const insertUrl = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${createResult.data.id}/data/import`
let insertResult let insertResult
try { try {
insertResult = await this.$axios.post(insertUrl, this.tableImport, this.config) insertResult = await dataImport(this.token, this.$route.params.container_id, this.$route.params.database_id, createResult.data.id, this.tableImport)
console.debug('inserted table', insertResult.data) console.debug('inserted table', insertResult.data)
} catch (err) { } catch (err) {
this.loading = false this.loading = false
......
...@@ -54,10 +54,10 @@ function formatUser (user) { ...@@ -54,10 +54,10 @@ function formatUser (user) {
if (!user) { if (!user) {
return null return null
} }
if (!('firstname' in user) || !('lastname' in user) || user.firstname === null || user.lastname === null) { if (!('given_name' in user) || !('family_name' in user) || user.given_name === null || user.family_name === null) {
return user?.username return user?.username
} }
return user.firstname + ' ' + user.lastname return user.given_name + ' ' + user.family_name
} }
function formatDateUTC (str) { function formatDateUTC (str) {
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
<hibernate-c3po.version>5.6.3.Final</hibernate-c3po.version> <hibernate-c3po.version>5.6.3.Final</hibernate-c3po.version>
<maven-report.version>3.0.0</maven-report.version> <maven-report.version>3.0.0</maven-report.version>
<jwt.version>4.3.0</jwt.version> <jwt.version>4.3.0</jwt.version>
<keycloak.version>21.0.2</keycloak.version>
</properties> </properties>
<dependencies> <dependencies>
...@@ -97,6 +98,11 @@ ...@@ -97,6 +98,11 @@
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
</dependency> </dependency>
<!-- Authentication --> <!-- Authentication -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.auth0</groupId> <groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId> <artifactId>java-jwt</artifactId>
......
...@@ -2,9 +2,13 @@ package at.tuwien.endpoint; ...@@ -2,9 +2,13 @@ package at.tuwien.endpoint;
import at.tuwien.api.auth.SignupRequestDto; import at.tuwien.api.auth.SignupRequestDto;
import at.tuwien.api.user.UserBriefDto; import at.tuwien.api.user.UserBriefDto;
import at.tuwien.entities.auth.Realm;
import at.tuwien.exception.RealmNotFoundException;
import at.tuwien.exception.RemoteUnavailableException; import at.tuwien.exception.RemoteUnavailableException;
import at.tuwien.exception.UserAlreadyExistsException;
import at.tuwien.exception.UserNotFoundException; import at.tuwien.exception.UserNotFoundException;
import at.tuwien.mapper.UserMapper; import at.tuwien.mapper.UserMapper;
import at.tuwien.service.RealmService;
import at.tuwien.service.UserService; import at.tuwien.service.UserService;
import io.micrometer.core.annotation.Timed; import io.micrometer.core.annotation.Timed;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
...@@ -28,11 +32,13 @@ public class UserEndpoint { ...@@ -28,11 +32,13 @@ public class UserEndpoint {
private final UserMapper userMapper; private final UserMapper userMapper;
private final UserService userService; private final UserService userService;
private final RealmService realmService;
@Autowired @Autowired
public UserEndpoint(UserMapper userMapper, UserService userService) { public UserEndpoint(UserMapper userMapper, UserService userService, RealmService realmService) {
this.userMapper = userMapper; this.userMapper = userMapper;
this.userService = userService; this.userService = userService;
this.realmService = realmService;
} }
@GetMapping @GetMapping
...@@ -54,9 +60,11 @@ public class UserEndpoint { ...@@ -54,9 +60,11 @@ public class UserEndpoint {
@Timed(value = "user.create", description = "Time needed to create a user in the metadata database") @Timed(value = "user.create", description = "Time needed to create a user in the metadata database")
@Operation(summary = "Create a user") @Operation(summary = "Create a user")
public ResponseEntity<UserBriefDto> create(@NotNull @Valid @RequestBody SignupRequestDto data) public ResponseEntity<UserBriefDto> create(@NotNull @Valid @RequestBody SignupRequestDto data)
throws UserNotFoundException, RemoteUnavailableException { throws UserNotFoundException, RemoteUnavailableException, RealmNotFoundException,
UserAlreadyExistsException {
log.debug("endpoint create a user, data={}", data); log.debug("endpoint create a user, data={}", data);
final UserBriefDto dto = userMapper.userToUserBriefDto(userService.create(data)); final Realm realm = realmService.find("dbrepo");
final UserBriefDto dto = userMapper.userToUserBriefDto(userService.create(data, realm));
log.trace("create user resulted in dto {}", dto); log.trace("create user resulted in dto {}", dto);
return ResponseEntity.status(HttpStatus.CREATED) return ResponseEntity.status(HttpStatus.CREATED)
.body(dto); .body(dto);
......
package at.tuwien.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.NOT_FOUND)
public class RealmNotFoundException extends Exception {
public RealmNotFoundException(String msg) {
super(msg);
}
public RealmNotFoundException(String msg, Throwable thr) {
super(msg, thr);
}
public RealmNotFoundException(Throwable thr) {
super(thr);
}
}
package at.tuwien.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.CONFLICT)
public class UserAlreadyExistsException extends Exception {
public UserAlreadyExistsException(String message) {
super(message);
}
public UserAlreadyExistsException(String message, Throwable thr) {
super(message, thr);
}
public UserAlreadyExistsException(Throwable thr) {
super(thr);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment