Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
IA2
GMS
Commits
04115082
Commit
04115082
authored
Aug 09, 2019
by
Sonia Zorba
Browse files
Added /ws endpoint and access to programmatic clients in BasicAuth
parent
9f9b7530
Changes
12
Hide whitespace changes
Inline
Side-by-side
gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java
View file @
04115082
...
...
@@ -12,6 +12,7 @@ import org.springframework.core.Ordered;
import
org.springframework.core.env.Environment
;
import
org.springframework.http.HttpMethod
;
import
org.springframework.security.config.annotation.web.builders.HttpSecurity
;
import
org.springframework.security.config.annotation.web.builders.WebSecurity
;
import
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
;
import
org.springframework.web.cors.CorsConfiguration
;
import
org.springframework.web.cors.UrlBasedCorsConfigurationSource
;
...
...
@@ -32,6 +33,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
super
.
configure
(
http
);
// CORS are necessary only for development (API access from npm server)
if
(
Arrays
.
asList
(
env
.
getActiveProfiles
()).
contains
(
"dev"
))
{
http
.
authorizeRequests
()
.
antMatchers
(
HttpMethod
.
OPTIONS
,
"/**"
).
permitAll
();
...
...
@@ -40,6 +42,31 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
http
.
csrf
().
disable
();
}
/**
* The authentication is ignored for these endpoints. The "/ws" endpoints
* (web service API for programmatic access) are protected by the custom
* WebServiceAuthorizationFilter that checks BasicAuth for GMS clients.
*/
@Override
public
void
configure
(
WebSecurity
web
)
throws
Exception
{
web
.
ignoring
().
antMatchers
(
"/ws/**"
,
"/error"
);
}
/**
* Checks the BasicAuth for GMS clients.
*/
@Bean
public
FilterRegistrationBean
webServiceAuthorizationFilter
()
{
FilterRegistrationBean
bean
=
new
FilterRegistrationBean
();
bean
.
setFilter
(
new
WebServiceAuthorizationFilter
());
bean
.
addUrlPatterns
(
"/ws/*"
);
bean
.
setOrder
(
Ordered
.
HIGHEST_PRECEDENCE
);
return
bean
;
}
/**
* CORS are necessary only for development (API access from npm server).
*/
@Bean
@Profile
(
"dev"
)
public
FilterRegistrationBean
corsFilter
()
{
...
...
gms/src/main/java/it/inaf/ia2/gms/authn/WebServiceAuthorizationFilter.java
0 → 100644
View file @
04115082
package
it.inaf.ia2.gms.authn
;
import
it.inaf.ia2.gms.exception.UnauthorizedException
;
import
it.inaf.ia2.gms.persistence.ClientsDAO
;
import
it.inaf.ia2.gms.persistence.model.ClientEntity
;
import
java.io.IOException
;
import
java.nio.charset.StandardCharsets
;
import
java.security.MessageDigest
;
import
java.security.NoSuchAlgorithmException
;
import
java.util.Base64
;
import
javax.servlet.Filter
;
import
javax.servlet.FilterChain
;
import
javax.servlet.ServletException
;
import
javax.servlet.ServletRequest
;
import
javax.servlet.ServletResponse
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
javax.xml.bind.DatatypeConverter
;
import
org.springframework.security.authentication.BadCredentialsException
;
import
org.springframework.web.context.WebApplicationContext
;
import
org.springframework.web.context.support.WebApplicationContextUtils
;
public
class
WebServiceAuthorizationFilter
implements
Filter
{
@Override
public
void
doFilter
(
ServletRequest
req
,
ServletResponse
res
,
FilterChain
chain
)
throws
IOException
,
ServletException
{
HttpServletRequest
request
=
(
HttpServletRequest
)
req
;
if
(
request
.
getServletPath
().
startsWith
(
"/ws/"
))
{
try
{
validateBasicAuth
(
request
);
}
catch
(
UnauthorizedException
ex
)
{
((
HttpServletResponse
)
res
).
sendError
(
HttpServletResponse
.
SC_UNAUTHORIZED
,
ex
.
getMessage
());
return
;
}
}
chain
.
doFilter
(
req
,
res
);
}
private
void
validateBasicAuth
(
HttpServletRequest
request
)
{
String
token
=
getBasicAuthToken
(
request
);
int
delim
=
token
.
indexOf
(
":"
);
if
(
delim
==
-
1
)
{
throw
new
BadCredentialsException
(
"Invalid basic authentication token"
);
}
String
clientId
=
token
.
substring
(
0
,
delim
);
String
clientSecret
=
token
.
substring
(
delim
+
1
);
ClientsDAO
clientsDAO
=
getClientsDAO
(
request
);
ClientEntity
client
=
clientsDAO
.
findClientById
(
clientId
)
.
orElseThrow
(()
->
new
BadCredentialsException
(
"Client "
+
clientId
+
" not found"
));
String
shaSecret
=
getSha256
(
clientSecret
);
if
(!
shaSecret
.
equals
(
client
.
getSecret
()))
{
throw
new
UnauthorizedException
(
"Wrong secret"
);
}
}
private
String
getBasicAuthToken
(
HttpServletRequest
request
)
{
String
header
=
request
.
getHeader
(
"Authorization"
);
if
(
header
==
null
||
!
header
.
toLowerCase
().
startsWith
(
"basic "
))
{
throw
new
UnauthorizedException
(
"Missing Authorization header"
);
}
byte
[]
base64Token
=
header
.
substring
(
6
).
getBytes
(
StandardCharsets
.
UTF_8
);
byte
[]
decoded
=
Base64
.
getDecoder
().
decode
(
base64Token
);
return
new
String
(
decoded
,
StandardCharsets
.
UTF_8
);
}
protected
ClientsDAO
getClientsDAO
(
HttpServletRequest
request
)
{
WebApplicationContext
webApplicationContext
=
WebApplicationContextUtils
.
getWebApplicationContext
(
request
.
getServletContext
());
return
webApplicationContext
.
getBean
(
ClientsDAO
.
class
);
}
private
static
String
getSha256
(
String
secret
)
{
try
{
MessageDigest
md
=
MessageDigest
.
getInstance
(
"SHA-256"
);
byte
[]
sha
=
md
.
digest
(
secret
.
getBytes
(
StandardCharsets
.
UTF_8
));
return
DatatypeConverter
.
printHexBinary
(
sha
).
toLowerCase
();
}
catch
(
NoSuchAlgorithmException
e
)
{
throw
new
IllegalStateException
(
e
);
}
}
}
gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
View file @
04115082
package
it.inaf.ia2.gms.controller
;
import
it.inaf.ia2.gms.authn.SessionData
;
import
it.inaf.ia2.gms.exception.UnauthorizedException
;
import
it.inaf.ia2.gms.model.CreateGroupRequest
;
import
it.inaf.ia2.gms.model.GroupNode
;
import
it.inaf.ia2.gms.model.GroupsModelRequest
;
import
it.inaf.ia2.gms.model.GroupsModelResponse
;
import
it.inaf.ia2.gms.model.PaginatedData
;
import
it.inaf.ia2.gms.model.PaginatedModelRequest
;
import
it.inaf.ia2.gms.model.Permission
;
import
it.inaf.ia2.gms.model.RenameGroupRequest
;
import
it.inaf.ia2.gms.persistence.model.GroupEntity
;
import
it.inaf.ia2.gms.service.GroupsModelBuilder
;
import
it.inaf.ia2.gms.service.GroupsService
;
import
it.inaf.ia2.gms.service.GroupsTreeBuilder
;
import
it.inaf.ia2.gms.service.PermissionsService
;
import
javax.validation.Valid
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.http.HttpStatus
;
...
...
@@ -31,6 +34,9 @@ public class GroupsController {
@Autowired
private
SessionData
session
;
@Autowired
private
PermissionsService
permissionsService
;
@Autowired
private
GroupsService
groupsService
;
...
...
@@ -48,7 +54,13 @@ public class GroupsController {
@PostMapping
(
value
=
"/group"
,
consumes
=
MediaType
.
APPLICATION_JSON_UTF8_VALUE
,
produces
=
MediaType
.
APPLICATION_JSON_UTF8_VALUE
)
public
ResponseEntity
<
PaginatedData
<
GroupNode
>>
createGroup
(
@Valid
@RequestBody
CreateGroupRequest
request
)
{
GroupEntity
newGroup
=
groupsService
.
addGroup
(
request
.
getParentGroupId
(),
request
.
getNewGroupName
(),
session
.
getUserId
());
GroupEntity
parent
=
groupsService
.
getGroupById
(
request
.
getParentGroupId
());
if
(
permissionsService
.
getGroupPermission
(
parent
,
session
.
getUserId
())
!=
Permission
.
ADMIN
)
{
throw
new
UnauthorizedException
(
"Missing admin privileges"
);
}
GroupEntity
newGroup
=
groupsService
.
addGroup
(
parent
,
request
.
getNewGroupName
());
PaginatedData
<
GroupNode
>
groupsPanel
=
getGroupsPanel
(
request
.
getParentGroupId
(),
request
);
...
...
gms/src/main/java/it/inaf/ia2/gms/controller/WebServiceController.java
0 → 100644
View file @
04115082
package
it.inaf.ia2.gms.controller
;
import
it.inaf.ia2.gms.persistence.model.GroupEntity
;
import
it.inaf.ia2.gms.service.GroupsService
;
import
java.util.List
;
import
java.util.Optional
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
@RestController
@RequestMapping
(
"/ws"
)
public
class
WebServiceController
{
@Autowired
private
GroupsService
groupsService
;
/**
* Creates a group and its ancestors if they are missing. It doesn't fail if
* the last group already exists.
*/
@PostMapping
(
"/group"
)
public
ResponseEntity
<
GroupEntity
>
createGroup
(
@RequestBody
List
<
String
>
names
)
{
GroupEntity
group
=
groupsService
.
getRoot
();
for
(
String
name
:
names
)
{
Optional
<
GroupEntity
>
optGroup
=
groupsService
.
findGroupByParentAndName
(
group
,
name
);
if
(
optGroup
.
isPresent
())
{
group
=
optGroup
.
get
();
}
else
{
group
=
groupsService
.
addGroup
(
group
,
name
);
}
}
return
new
ResponseEntity
<>(
group
,
HttpStatus
.
CREATED
);
}
public
void
deleteGroup
()
{
}
public
void
addMember
()
{
}
public
void
removeMember
()
{
}
public
void
addPrivilege
()
{
}
public
void
deletePrivilege
()
{
}
public
void
prepareToJoin
()
{
}
}
gms/src/main/java/it/inaf/ia2/gms/persistence/ClientsDAO.java
0 → 100644
View file @
04115082
package
it.inaf.ia2.gms.persistence
;
import
it.inaf.ia2.gms.persistence.model.ClientEntity
;
import
java.sql.PreparedStatement
;
import
java.sql.ResultSet
;
import
java.sql.SQLException
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Optional
;
import
javax.sql.DataSource
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.jdbc.core.JdbcTemplate
;
import
org.springframework.stereotype.Component
;
@Component
public
class
ClientsDAO
{
private
final
JdbcTemplate
jdbcTemplate
;
@Autowired
public
ClientsDAO
(
DataSource
dataSource
)
{
jdbcTemplate
=
new
JdbcTemplate
(
dataSource
);
}
public
Optional
<
ClientEntity
>
findClientById
(
String
clientId
)
{
String
sql
=
"SELECT client_secret, allowed_actions, ip_filter FROM gms_client WHERE client_id = ?"
;
return
jdbcTemplate
.
query
(
conn
->
{
PreparedStatement
ps
=
conn
.
prepareStatement
(
sql
);
ps
.
setString
(
1
,
clientId
);
return
ps
;
},
resultSet
->
{
if
(
resultSet
.
next
())
{
ClientEntity
client
=
new
ClientEntity
();
client
.
setId
(
clientId
);
client
.
setSecret
(
resultSet
.
getString
(
"client_secret"
));
client
.
setAllowedActions
(
getAllowedActions
(
resultSet
));
client
.
setIpFilter
(
resultSet
.
getString
(
"ip_filter"
));
return
Optional
.
of
(
client
);
}
return
Optional
.
empty
();
});
}
private
List
<
String
>
getAllowedActions
(
ResultSet
resultSet
)
throws
SQLException
{
List
<
String
>
actions
=
new
ArrayList
<>();
ResultSet
items
=
resultSet
.
getArray
(
"allowed_actions"
).
getResultSet
();
while
(
items
.
next
())
{
String
action
=
items
.
getString
(
1
);
actions
.
add
(
action
);
}
return
actions
;
}
}
gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
View file @
04115082
...
...
@@ -101,6 +101,27 @@ public class GroupsDAO {
});
}
public
Optional
<
GroupEntity
>
findGroupByParentAndName
(
String
parentPath
,
String
childName
)
{
String
sql
=
"SELECT id, path from gms_group WHERE name = ? AND path ~ ?"
;
return
jdbcTemplate
.
query
(
conn
->
{
PreparedStatement
ps
=
conn
.
prepareStatement
(
sql
);
ps
.
setString
(
1
,
childName
);
ps
.
setObject
(
2
,
getSubGroupsPath
(
parentPath
),
Types
.
OTHER
);
return
ps
;
},
resultSet
->
{
if
(
resultSet
.
next
())
{
GroupEntity
group
=
new
GroupEntity
();
group
.
setId
(
resultSet
.
getString
(
"id"
));
group
.
setName
(
childName
);
group
.
setPath
(
resultSet
.
getString
(
"path"
));
return
Optional
.
of
(
group
);
}
return
Optional
.
empty
();
});
}
public
List
<
GroupEntity
>
listSubGroups
(
String
path
)
{
String
sql
=
"SELECT id, name, path from gms_group WHERE path ~ ? ORDER BY name"
;
...
...
gms/src/main/java/it/inaf/ia2/gms/persistence/model/ClientEntity.java
0 → 100644
View file @
04115082
package
it.inaf.ia2.gms.persistence.model
;
import
java.util.List
;
public
class
ClientEntity
{
private
String
id
;
private
String
secret
;
private
List
<
String
>
allowedActions
;
private
String
ipFilter
;
public
String
getId
()
{
return
id
;
}
public
void
setId
(
String
id
)
{
this
.
id
=
id
;
}
public
String
getSecret
()
{
return
secret
;
}
public
void
setSecret
(
String
secret
)
{
this
.
secret
=
secret
;
}
public
List
<
String
>
getAllowedActions
()
{
return
allowedActions
;
}
public
void
setAllowedActions
(
List
<
String
>
allowedActions
)
{
this
.
allowedActions
=
allowedActions
;
}
public
String
getIpFilter
()
{
return
ipFilter
;
}
public
void
setIpFilter
(
String
ipFilter
)
{
this
.
ipFilter
=
ipFilter
;
}
}
gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
View file @
04115082
...
...
@@ -9,6 +9,7 @@ import it.inaf.ia2.gms.model.Permission;
import
it.inaf.ia2.gms.persistence.GroupsDAO
;
import
it.inaf.ia2.gms.persistence.model.GroupEntity
;
import
java.util.List
;
import
java.util.Optional
;
import
java.util.UUID
;
@Service
...
...
@@ -37,13 +38,7 @@ public class GroupsService {
}
}
public
GroupEntity
addGroup
(
String
parentId
,
String
groupName
,
String
userId
)
{
GroupEntity
parent
=
getGroupById
(
parentId
);
if
(
permissionsService
.
getGroupPermission
(
parent
,
userId
)
!=
Permission
.
ADMIN
)
{
throw
new
UnauthorizedException
(
"Missing admin privileges"
);
}
public
GroupEntity
addGroup
(
GroupEntity
parent
,
String
groupName
)
{
if
(
groupsDAO
.
listSubGroups
(
parent
.
getPath
()).
stream
()
.
anyMatch
(
g
->
g
.
getName
().
equals
(
groupName
)))
{
...
...
@@ -107,6 +102,10 @@ public class GroupsService {
return
parent
;
}
public
GroupEntity
getRoot
()
{
return
getGroupById
(
ROOT
);
}
public
GroupEntity
getGroupById
(
String
groupId
)
{
return
groupsDAO
.
findGroupById
(
groupId
)
.
orElseThrow
(()
->
new
BadRequestException
(
"Group "
+
groupId
+
" not found"
));
...
...
@@ -120,4 +119,8 @@ public class GroupsService {
public
List
<
GroupBreadcrumb
>
getBreadcrumbs
(
String
path
)
{
return
groupsDAO
.
getBreadcrumbs
(
path
);
}
public
Optional
<
GroupEntity
>
findGroupByParentAndName
(
GroupEntity
parent
,
String
childName
)
{
return
groupsDAO
.
findGroupByParentAndName
(
parent
.
getPath
(),
childName
);
}
}
gms/src/main/resources/sql/init.sql
View file @
04115082
...
...
@@ -29,3 +29,11 @@ CREATE TABLE gms_permission (
foreign
key
(
group_id
)
references
gms_group
(
id
),
foreign
key
(
group_path
)
references
gms_group
(
path
)
);
CREATE
TABLE
gms_client
(
client_id
varchar
NOT
NULL
,
client_secret
varchar
NOT
NULL
,
allowed_actions
text
[]
NOT
NULL
,
ip_filter
text
NULL
,
primary
key
(
client_id
)
);
\ No newline at end of file
gms/src/test/java/it/inaf/ia2/gms/authn/WebServiceAuthorizationFilterTest.java
0 → 100644
View file @
04115082
package
it.inaf.ia2.gms.authn
;
import
it.inaf.ia2.gms.persistence.ClientsDAO
;
import
it.inaf.ia2.gms.persistence.model.ClientEntity
;
import
java.util.Collections
;
import
java.util.Optional
;
import
javax.servlet.FilterChain
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.junit.runners.JUnit4
;
import
static
org
.
mockito
.
ArgumentMatchers
.
any
;
import
static
org
.
mockito
.
ArgumentMatchers
.
anyInt
;
import
static
org
.
mockito
.
ArgumentMatchers
.
eq
;
import
static
org
.
mockito
.
Mockito
.
doReturn
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
mockito
.
Mockito
.
never
;
import
static
org
.
mockito
.
Mockito
.
spy
;
import
static
org
.
mockito
.
Mockito
.
times
;
import
static
org
.
mockito
.
Mockito
.
verify
;
import
static
org
.
mockito
.
Mockito
.
when
;
@RunWith
(
JUnit4
.
class
)
public
class
WebServiceAuthorizationFilterTest
{
private
WebServiceAuthorizationFilter
filter
;
private
HttpServletRequest
request
;
private
HttpServletResponse
response
;
private
FilterChain
chain
;
@Before
public
void
setUp
()
{
ClientsDAO
clientsDAO
=
mock
(
ClientsDAO
.
class
);
ClientEntity
client
=
new
ClientEntity
();
client
.
setId
(
"test"
);
client
.
setSecret
(
"5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
);
// sha256 of "password"
client
.
setAllowedActions
(
Collections
.
singletonList
(
"*"
));
when
(
clientsDAO
.
findClientById
(
"test"
)).
thenReturn
(
Optional
.
of
(
client
));
filter
=
spy
(
new
WebServiceAuthorizationFilter
());
doReturn
(
clientsDAO
).
when
(
filter
).
getClientsDAO
(
any
());
request
=
mock
(
HttpServletRequest
.
class
);
response
=
mock
(
HttpServletResponse
.
class
);
chain
=
mock
(
FilterChain
.
class
);
}
@Test
public
void
testValidCredentials
()
throws
Exception
{
when
(
request
.
getServletPath
()).
thenReturn
(
"/ws/group"
);
when
(
request
.
getHeader
(
"Authorization"
)).
thenReturn
(
"Basic dGVzdDpwYXNzd29yZA=="
);
// test:password
filter
.
doFilter
(
request
,
response
,
chain
);
verify
(
chain
,
times
(
1
)).
doFilter
(
any
(),
any
());
}
@Test
public
void
testInvalidCredentials
()
throws
Exception
{
when
(
request
.
getServletPath
()).
thenReturn
(
"/ws/group"
);
when
(
request
.
getHeader
(
"Authorization"
)).
thenReturn
(
"Basic dGVzdDp0ZXN0"
);
// test:test
filter
.
doFilter
(
request
,
response
,
chain
);
verify
(
response
,
times
(
1
)).
sendError
(
eq
(
HttpServletResponse
.
SC_UNAUTHORIZED
),
any
());
verify
(
chain
,
never
()).
doFilter
(
any
(),
any
());
}
@Test
public
void
testMissingHeader
()
throws
Exception
{
when
(
request
.
getServletPath
()).
thenReturn
(
"/ws/group"
);
filter
.
doFilter
(
request
,
response
,
chain
);
verify
(
response
,
times
(
1
)).
sendError
(
eq
(
HttpServletResponse
.
SC_UNAUTHORIZED
),
any
());
verify
(
chain
,
never
()).
doFilter
(
any
(),
any
());
}
@Test
public
void
testOutsidePath
()
throws
Exception
{
when
(
request
.
getServletPath
()).
thenReturn
(
"/other/path"
);
filter
.
doFilter
(
request
,
response
,
chain
);
verify
(
response
,
never
()).
sendError
(
anyInt
(),
any
());
verify
(
chain
,
times
(
1
)).
doFilter
(
any
(),
any
());
}
}
gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
View file @
04115082
...
...
@@ -88,6 +88,15 @@ public class GroupsDAOTest {
assertEquals
(
1
,
groups
.
size
());
assertEquals
(
"INAF"
,
groups
.
get
(
0
).
getName
());
// Group by parent and name
Optional
<
GroupEntity
>
optGroup
=
dao
.
findGroupByParentAndName
(
root
.
getPath
(),
lbt
.
getName
());
assertTrue
(
optGroup
.
isPresent
());
assertEquals
(
lbt
.
getId
(),
optGroup
.
get
().
getId
());
optGroup
=
dao
.
findGroupByParentAndName
(
lbt
.
getPath
(),
lbtInaf
.
getName
());
assertTrue
(
optGroup
.
isPresent
());
assertEquals
(
lbtInaf
.
getId
(),
optGroup
.
get
().
getId
());
// Children map
Map
<
String
,
Boolean
>
childrenMap
=
dao
.
getHasChildrenMap
(
Sets
.
newSet
(
root
.
getId
()));
assertEquals
(
1
,
childrenMap
.
size
());
...
...
gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
View file @
04115082
...
...
@@ -49,13 +49,13 @@ public class NestedGroupsIntegrationTest {
permissionsDAO
.
createPermission
(
superAdminPermission
);
// Setup groups
GroupEntity
root
=
groupsService
.
get
GroupById
(
GroupsService
.
ROOT
);
GroupEntity
lbt
=
groupsService
.
addGroup
(
G
ro
upsService
.
ROOT
,
"LBT"
,
userId
);
GroupEntity
tng
=
groupsService
.
addGroup
(
G
ro
upsService
.
ROOT
,
"TNG"
,
userId
);
GroupEntity
radio
=
groupsService
.
addGroup
(
G
ro
upsService
.
ROOT
,
"Radio"
,
userId
);
GroupEntity
lbtInaf
=
groupsService
.
addGroup
(
lbt
.
getId
(),
"INAF"
,
userId
);
GroupEntity
lbtInafProgram1
=
groupsService
.
addGroup
(
lbtInaf
.
getId
(),
"P1"
,
userId
);
GroupEntity
lbtInafProgram2
=
groupsService
.
addGroup
(
lbtInaf
.
getId
(),
"P2"
,
userId
);
GroupEntity
root
=
groupsService
.
get
Root
(
);
GroupEntity
lbt
=
groupsService
.
addGroup
(
ro
ot
,
"LBT"
);
GroupEntity
tng
=
groupsService
.
addGroup
(
ro
ot
,
"TNG"
);