Changeset 976

Show
Ignore:
Timestamp:
21-10-10 17:28:04 (4 years ago)
Author:
robert@…
Message:

Authentication and authorization for studies is added, according to ticket 118

Location:
trunk
Files:
39 added
20 modified

Legend:

Unmodified
Added
Removed
  • trunk/application.properties

    r972 r976  
    99app.servlet.version=2.4 
    1010app.version=0.5.0 
    11 plugins.aaaa=0.3.6 
    1211plugins.crypto=2.0 
    1312plugins.db-util=0.4 
     13plugins.famfamfam=1.0.1 
    1414plugins.grom=0.1.2 
    1515plugins.hibernate=1.3.5 
    16 plugins.jquery=1.4.3.2 
     16plugins.jquery=1.4.2.5 
     17plugins.jquery-ui=1.8.4.3 
    1718plugins.mail=0.9 
     19plugins.nadd-neutralizer=0.3 
    1820plugins.oauth=0.10 
    1921plugins.searchable=0.5.5.1 
    2022plugins.spring-security-core=1.0.1 
     23plugins.spring-security-ui=0.1.2 
    2124plugins.tomcat=1.3.5 
    22 plugins.webflow=1.3.5 
     25plugins.webflow=1.3.4 
     26 
  • trunk/grails-app/conf/BaseFilters.groovy

    r776 r976  
    2929                        } 
    3030                } 
     31 
    3132        } 
    3233} 
  • trunk/grails-app/conf/BootStrap.groovy

    r939 r976  
    66import org.codehaus.groovy.grails.commons.GrailsApplication 
    77import grails.util.GrailsUtil 
    8 import org.nmcdsp.plugins.aaaa.SecUser 
     8import dbnp.authentication.* 
    99 
    1010 
     
    2626                System.setProperty('user.timezone', 'CET') 
    2727 
    28                 def user = SecUser.findByUsername('user') ?: new SecUser( 
     28                def adminRole = SecRole.findByAuthority( 'ROLE_ADMIN' ) ?: new SecRole( authority: 'ROLE_ADMIN' ).save() 
     29 
     30                def user = SecUser.findByUsername('user') ?: new SecUser( 
    2931                           username: 'user', 
    30                            password: springSecurityService.encodePassword('useR123!'), 
    31                            enabled: true).save(failOnError: true) 
     32                           password: springSecurityService.encodePassword( 'useR123!', 'user' ), 
     33                           email: 'user@dbnp.org', 
     34                           userConfirmed: true, adminConfirmed: true).save(failOnError: true) 
    3235 
    3336                def userAdmin = SecUser.findByUsername('admin') ?: new SecUser( 
    3437                                username: 'admin', 
    35                                 password: springSecurityService.encodePassword('admiN123!'), 
    36                                 enabled: true).save(failOnError: true) 
     38                                password: springSecurityService.encodePassword( 'admiN123!', 'admin' ), 
     39                                email: 'admin@dbnp.org', 
     40                                userConfirmed: true, adminConfirmed: true).save(failOnError: true) 
     41 
     42                // Make the admin user an administrator 
     43                SecUserSecRole.create userAdmin, adminRole, true 
    3744 
    3845                def userTest = SecUser.findByUsername('test') ?: new SecUser( 
    3946                                username: 'test', 
    40                                 password: springSecurityService.encodePassword('testT123!'), 
    41                                 enabled: true).save(failOnError: true) 
    42                  
    43                 println "Done with SpringSecurity bootstrap, created [user, admin, test]." 
     47                                password: springSecurityService.encodePassword( 'useR123!', 'test' ), 
     48                                email: 'test@dbnp.org', 
     49                            userConfirmed: true, adminConfirmed: true).save(failOnError: true) 
     50 
     51                println "Done with SpringSecurity bootstrap, created [user, admin, test]." 
    4452 
    4553                // If there are no templates yet in the database 
     
    8189                                } else { 
    8290                                        // general study boostrapping 
    83                                         BootStrapStudies.addExampleStudies(user) 
     91                                        BootStrapStudies.addExampleStudies(user, userAdmin) 
    8492                                } 
    8593                        } 
  • trunk/grails-app/conf/BootStrapStudies.groovy

    r948 r976  
    2121         */ 
    2222 
    23         public static void addExampleStudies(org.nmcdsp.plugins.aaaa.SecUser owner) { 
     23        public static void addExampleStudies(dbnp.authentication.SecUser owner, dbnp.authentication.SecUser otherUser ) { 
    2424 
    2525                // Look up the used ontologies which should be in the database by now 
     
    171171                        ecCode:"2007117.c", 
    172172                        startDate: Date.parse('yyyy-MM-dd','2008-01-02'), 
    173                         owner: owner 
     173                        owner: owner, 
     174                        readers: [otherUser] 
    174175                ).with { if (!validate()) { errors.each { println it} } else save()} 
    175176 
     
    378379                        ecCode:"unknown", 
    379380                        startDate: Date.parse('yyyy-MM-dd','2008-01-14'), 
    380                         owner: owner 
     381                        owner: owner, 
     382                        writers: [otherUser] 
    381383                ) 
    382384                .setFieldValue( 'Description', "Human study performed at RRI; centres involved: RRI, IFR, TUM, Maastricht U." ) 
  • trunk/grails-app/conf/Config.groovy

    r936 r976  
    157157 
    158158// Needed for the Spring Security Core plugin: 
    159 grails.plugins.springsecurity.userLookup.userDomainClassName = 'org.nmcdsp.plugins.aaaa.SecUser' 
    160 grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'org.nmcdsp.plugins.aaaa.SecUserSecRole' 
    161 grails.plugins.springsecurity.authority.className = 'org.nmcdsp.plugins.aaaa.SecRole' 
     159grails.plugins.springsecurity.userLookup.userDomainClassName = 'dbnp.authentication.SecUser' 
     160grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'dbnp.authentication.SecUserSecRole' 
     161grails.plugins.springsecurity.authority.className = 'dbnp.authentication.SecRole' 
     162grails.plugins.springsecurity.password.algorithm = 'SHA-256' 
     163grails.plugins.springsecurity.password.encodeHashAsBase64 = true 
     164grails.plugins.springsecurity.dao.reflectionSaltSourceProperty = 'username' // Use the persons username as salt for encryption 
     165grails.plugins.springsecurity.securityConfigType = grails.plugins.springsecurity.SecurityConfigType.Annotation 
     166 
     167// Make sure the different controllers provided by springsecurity.ui are only accessible by administrators 
     168// NB: the RegisterController is used for forgotten passwords. It should be accessible by anyone 
     169grails.plugins.springsecurity.controllerAnnotations.staticRules = [ 
     170    '/user/**': ['ROLE_ADMIN'], 
     171    '/role/**': ['ROLE_ADMIN'], 
     172    '/aclclass/**': ['ROLE_ADMIN'], 
     173    '/aclentry/**': ['ROLE_ADMIN'], 
     174    '/aclobjectidentity/**': ['ROLE_ADMIN'], 
     175    '/aclsid/**': ['ROLE_ADMIN'], 
     176    '/persistentlogin/**': ['ROLE_ADMIN'], 
     177    '/registrationcode/**': ['ROLE_ADMIN'], 
     178    '/requestmap/**': ['ROLE_ADMIN'], 
     179    '/securityinfo/**': ['ROLE_ADMIN'] 
     180] 
     181 
     182 
     183// Needed for sending emails 
     184grails { 
     185    mail { 
     186        host = "smtp.gmail.com" 
     187        port = 465 
     188        username = "gscfproject@gmail.com" 
     189        password = "gscf2010" 
     190        props = [ 
     191            "mail.smtp.auth":"true", 
     192            "mail.smtp.socketFactory.port": '465', 
     193            "mail.smtp.socketFactory.class": "javax.net.ssl.SSLSocketFactory", 
     194            "mail.smtp.socketFactory.fallback": "false" 
     195        ] 
     196    } 
     197} 
    162198 
    163199// The OAuth consumer key and secret variables are currently replaced by Jeroen's Continuous Integration script. 
     
    173209        } 
    174210} 
     211 
  • trunk/grails-app/controllers/dbnp/importer/ImporterController.groovy

    r959 r976  
    3131import grails.converters.JSON 
    3232import org.apache.poi.hssf.usermodel.HSSFWorkbook 
    33  
     33import grails.plugins.springsecurity.Secured 
     34 
     35@Secured(['IS_AUTHENTICATED_REMEMBERED']) 
    3436class ImporterController { 
    3537    def ImporterService     
  • trunk/grails-app/controllers/dbnp/studycapturing/StudyController.groovy

    r959 r976  
    22 
    33import grails.converters.* 
     4import grails.plugins.springsecurity.Secured 
     5 
    46 
    57/** 
     
    79 */ 
    810class StudyController { 
    9  
     11    def AuthenticationService 
     12     
    1013    //static allowedMethods = [save: "POST", update: "POST", delete: "POST"] 
    1114 
     
    1417    } 
    1518 
     19    /** 
     20     * Shows all studies where the user has access to 
     21     */ 
    1622    def list = { 
    17         params.max = Math.min(params.max ? params.int('max') : 10, 100) 
    18         [studyInstanceList: Study.list(params), studyInstanceTotal: Study.count()] 
     23 
     24        def user = AuthenticationService.getLoggedInUser() 
     25        def max = Math.min(params.max ? params.int('max') : 10, 100) 
     26 
     27        def c = Study.createCriteria() 
     28 
     29        def studies 
     30        if( user == null ) { 
     31            studies = c.list { 
     32                maxResults(max) 
     33                and { 
     34                    eq( "published", true ) 
     35                    eq( "publicstudy", true ) 
     36                } 
     37            } 
     38        } else { 
     39            studies = c.list { 
     40                maxResults(max) 
     41                or { 
     42                    eq( "owner", user ) 
     43                    writers { 
     44                        eq( "id", user.id ) 
     45                    } 
     46                    and { 
     47                        readers { 
     48                            eq( "id", user.id ) 
     49                        } 
     50                        eq( "published", true ) 
     51                    } 
     52                } 
     53            } 
     54        } 
     55         
     56        [studyInstanceList: studies, studyInstanceTotal: studies.count()] 
     57    } 
     58 
     59    /** 
     60     * Shows studies for which the logged in user is the owner 
     61     */ 
     62    @Secured(['IS_AUTHENTICATED_REMEMBERED']) 
     63    def myStudies = { 
     64        def user = AuthenticationService.getLoggedInUser() 
     65        def max = Math.min(params.max ? params.int('max') : 10, 100) 
     66 
     67        def studies = Study.findAllByOwner(user); 
     68        render( view: "list", model: [studyInstanceList: studies, studyInstanceTotal: studies.count()] ) 
    1969    } 
    2070 
     
    4393        } 
    4494        else { 
     95            // Check whether the user may see this study 
     96            def loggedInUser = AuthenticationService.getLoggedInUser() 
     97            if( !studyInstance.canRead(loggedInUser) ) { 
     98                flash.message = "You have no access to this study" 
     99                redirect(action: "list") 
     100            } 
     101 
    45102            // The study instance is packed into an array, to be able to 
    46103            // use the same view for showing the study and comparing multiple 
    47104            // studies 
    48             [studyList: [ studyInstance ], multipleStudies: false ] 
    49         } 
    50     } 
    51  
    52         def showByToken = { 
     105            [studyList: [ studyInstance ], multipleStudies: false, loggedInUser: loggedInUser ] 
     106        } 
     107    } 
     108 
     109    def showByToken = { 
    53110        def studyInstance = Study.findByCode(params.id) 
    54111        if (!studyInstance) { 
     
    57114        } 
    58115        else { 
     116            // Check whether the user may see this study 
     117            def loggedInUser = AuthenticationService.getLoggedInUser() 
     118            if( !studyInstance.canRead(loggedInUser) ) { 
     119                flash.message = "You have no access to this study" 
     120                redirect(action: "list") 
     121            } 
     122 
    59123            redirect(action: "show", id: studyInstance.id) 
    60124        } 
  • trunk/grails-app/controllers/dbnp/studycapturing/WizardController.groovy

    r959 r976  
    55// Grails convertors is imported in order to create JSON objects 
    66import grails.converters.* 
     7import grails.plugins.springsecurity.Secured 
     8import dbnp.authentication.AuthenticationService 
     9import dbnp.authentication.SecUser 
    710 
    811 
     
    1215 * The wizard controller handles the handeling of pages and data flow 
    1316 * through the study capturing wizard. 
     17 * 
     18 * This controller is only accessible by logged in users. 
    1419 * 
    1520 * @author Jeroen Wesbeek 
     
    2227 * $Date$ 
    2328 */ 
     29@Secured(['IS_AUTHENTICATED_REMEMBERED']) 
    2430class WizardController { 
     31        def AuthenticationService 
     32         
    2533        /** 
    2634         * index method, redirect to the webflow 
     
    790798                                        // save study 
    791799                                        println ".saving study" 
     800 
     801                                        // Make sure the owner of the study is set right 
     802                                        flow.study.owner = AuthenticationService.getLoggedInUser() 
     803                                         
    792804                                        if (!flow.study.save(flush:true)) { 
    793805                                                this.appendErrors(flow.study, flash.errors) 
     
    846858                try { 
    847859                        // load study 
    848                         flow.study = (params.studyid) ? Study.findById( params.studyid ) : Study.findByTitle( params.study ) 
    849  
     860                        def study = (params.studyid) ? Study.findById( params.studyid ) : Study.findByTitle( params.study ) 
     861 
     862                        // Check whether the user is allowed to edit this study. If it is not allowed 
     863                        // the used should had never seen a link to this page, so he should never get 
     864                        // here. That's why we just return false 
     865                        if( !study.canWrite(AuthenticationService.getLoggedInUser()) ) { 
     866                            return false; 
     867                        } 
     868 
     869                        flow.study = study 
    850870                        // set 'quicksave' variable 
    851871                        flow.quickSave = true 
     
    903923                // handle contacts 
    904924                handleContacts(flow, flash, params) 
     925 
     926                // handle users (readers, writers) 
     927                handleUsers(flow, flash, params, 'readers') 
     928                handleUsers(flow, flash, params, 'writers') 
     929 
     930                // handle public checkbox 
     931                if( params.get( "publicstudy" ) ) { 
     932                    flow.study.publicstudy = params.get( "publicstudy" ) 
     933                } 
    905934 
    906935                // validate the study 
     
    10161045 
    10171046        } 
     1047 
     1048        /** 
     1049         * re-usable code for handling contacts form data in a web flow 
     1050         * @param Map LocalAttributeMap (the flow scope) 
     1051         * @param Map localAttributeMap (the flash scope) 
     1052         * @param Map GrailsParameterMap (the flow parameters = form data) 
     1053         * @param String    'readers' or 'writers'  
     1054         * @return boolean 
     1055         */ 
     1056        def handleUsers(flow, flash, params, type) { 
     1057                def users = [] 
     1058 
     1059                if( type == "readers" ) { 
     1060                    users = flow.study.readers ?: [] 
     1061                } else if( type == "writers" ) { 
     1062                    users = flow.study.writers ?: [] 
     1063                } 
     1064 
     1065                // Check the ids of the contacts that should be attached 
     1066                // to this study. If they are already attached, keep 'm. If 
     1067                // studies are attached that are not in the selected (i.e. the 
     1068                // user deleted them), remove them 
     1069 
     1070                // Users are saved as user_id 
     1071                def userIDs = params.get( type + '_ids') 
     1072                if (userIDs) { 
     1073                        // Find the individual IDs and make integers 
     1074                        userIDs = userIDs.split(',').collect { Integer.parseInt(it, 10) } 
     1075 
     1076                        // First remove the publication that are not present in the array 
     1077                        users.removeAll { user -> !userIDs.find { id -> id == user.id } } 
     1078 
     1079                        // Add those publications not yet present in the database 
     1080                        userIDs.each { id -> 
     1081                                if (!users.find { user -> id == user.id }) { 
     1082                                        def user = SecUser.get(id) 
     1083                                        if (user) { 
     1084                                            users.add(user) 
     1085                                        } else { 
     1086                                                println('.user with ID ' + id + ' not found in database.') 
     1087                                        } 
     1088                                } 
     1089                        } 
     1090 
     1091                } else { 
     1092                        println('.no users selected.') 
     1093                        users.clear() 
     1094                } 
     1095 
     1096                if( type == "readers" ) { 
     1097                    if( flow.study.readers ) 
     1098                        flow.study.readers.clear() 
     1099                    users.each { flow.study.addToReaders( it ) } 
     1100                } else if( type == "writers" ) { 
     1101                    if( flow.study.writers ) 
     1102                        flow.study.writers.clear() 
     1103                         
     1104                    users.each { flow.study.addToWriters( it ) } 
     1105                } 
     1106 
     1107        } 
    10181108                                                  
    10191109        /** 
  • trunk/grails-app/controllers/RestController.groovy

    r963 r976  
    1818import dbnp.studycapturing.Study 
    1919import dbnp.studycapturing.Assay 
    20 import org.nmcdsp.plugins.aaaa.SecUser 
     20import dbnp.authentication.SecUser 
    2121import grails.converters.* 
    2222import nl.metabolomicscentre.dsp.http.BasicAuthentication 
  • trunk/grails-app/domain/dbnp/studycapturing/Study.groovy

    r974 r976  
    11package dbnp.studycapturing 
    22 
    3 import org.nmcdsp.plugins.aaaa.SecUser 
     3import dbnp.authentication.SecUser 
    44 
    55/** 
     
    1212 */ 
    1313class Study extends TemplateEntity { 
    14         static searchable = { 
    15         [only: ['title', 'Description']] // the description field will be searched only if defined in a study template 
    16     } 
     14        static searchable = { 
     15            [only: ['title', 'Description']] // the description field will be searched only if defined in a study template 
     16        } 
    1717 
    1818        SecUser owner           // The owner of the study. A new study is automatically owned by its creator. 
     
    2929        List assays 
    3030        boolean published = false // Determines whether a study is private (only accessable by the owner and writers) or published (also visible to readers) 
    31  
     31        boolean publicstudy = false  // Determines whether anonymous users are allowed to see this study. This has only effect when published = true 
     32         
    3233        static hasMany = [               
    3334                subjects: Subject, 
     
    3839                assays: Assay, 
    3940                persons: StudyPerson, 
    40                 publications: Publication 
     41                publications: Publication, 
     42                readers: SecUser, 
     43                writers: SecUser 
    4144        ] 
    4245 
     
    5457                // Workaround for bug http://jira.codehaus.org/browse/GRAILS-6754 
    5558                templateTextFields type: 'text' 
    56                 owner column: "studyowner" 
    57                 title column: "studytitle" 
    58                 code column: "studycode" 
    59                 subjects column: "studysubjects" 
    60                 events column: "studyevents" 
    61                 samplingEvents column: "studysamplingevents" 
    62                 eventGroups column: "studyeventgroups" 
    63                 samples column: "studysamples" 
    64                 assays column: "studyassays" 
    6559        } 
    6660 
     
    407401                return msg 
    408402        } 
     403 
     404    /** 
     405     * Returns true if the given user is allowed to read this study 
     406     */ 
     407    public boolean canRead(SecUser loggedInUser) { 
     408        // Anonymous readers are only given access when published and public 
     409        if( loggedInUser == null ) { 
     410            return this.publicstudy && this.published; 
     411        } 
     412 
     413        // Owners and writers are allowed to read this study 
     414        if( this.owner == loggedInUser || this.writers.contains(loggedInUser) ) { 
     415            return true 
     416        } 
     417             
     418        // Readers are allowed to read this study when it is published 
     419        if( this.readers.contains(loggedInUser) && this.published ) { 
     420            return true 
     421        } 
     422         
     423        return false 
     424    } 
     425 
     426    /** 
     427     * Returns true if the given user is allowed to write this study 
     428     */ 
     429    public boolean canWrite(SecUser loggedInUser) { 
     430        if( loggedInUser == null ) { 
     431            return false; 
     432        } 
     433        return this.owner == loggedInUser || this.writers.contains(loggedInUser) 
     434    } 
     435 
     436    /** 
     437     * Returns true if the given user is the owner of this study 
     438     */ 
     439    public boolean isOwner(SecUser loggedInUser) { 
     440        if( loggedInUser == null ) { 
     441            return false; 
     442        } 
     443        return this.owner == loggedInUser 
     444    } 
     445 
    409446} 
  • trunk/grails-app/domain/dbnp/studycapturing/TemplateEntity.groovy

    r961 r976  
    7070                // Make sure that the text fields are really stored as TEXT, so that those Strings can have an arbitrary length. 
    7171                templateTextFields type: 'text' 
    72  
    73                 template column:"templateentitytemplate" 
    7472        } 
    7573 
  • trunk/grails-app/domain/dbnp/studycapturing/Template.groovy

    r961 r976  
    11package dbnp.studycapturing 
    22 
    3 import org.nmcdsp.plugins.aaaa.SecUser 
     3import dbnp.authentication.SecUser 
    44 
    55/** 
     
    3939 
    4040        static mapping = { 
    41                 name column:"templatename" 
    42                 description column:"templatedescription" 
    43                 entity column:"templateentity" 
    44                 owner column:"templateowner" 
    45                 fields column:"templatefields" 
    4641        } 
    4742 
  • trunk/grails-app/taglib/dbnp/studycapturing/WizardTagLib.groovy

    r959 r976  
    33import org.codehaus.groovy.grails.plugins.web.taglib.JavascriptTagLib 
    44import dbnp.studycapturing.* 
     5import dbnp.authentication.SecUser 
    56import dbnp.data.* 
    67import cr.co.arquetipos.crypto.Blowfish 
     
    1920 */ 
    2021class WizardTagLib extends JavascriptTagLib { 
     22 
     23        def AuthenticationService 
     24         
    2125        // define the tag namespace (e.g.: <wizard:action ... /> 
    2226        static namespace = "wizard" 
     
    649653         */ 
    650654        def studySelect = { attrs -> 
    651                 // for now, just fetch all studies 
    652                 attrs.from = Study.findAll() 
     655                // Find all studies the user has access to 
     656                def user = AuthenticationService.getLoggedInUser() 
     657 
     658                def c = Study.createCriteria() 
     659                attrs.from = c.list { 
     660                    or { 
     661                        eq( "owner", user ) 
     662                        writers { 
     663                            eq( "id", user.id ) 
     664                        } 
     665                    } 
     666                } 
    653667 
    654668                // got a name? 
     
    13111325                out << "Role: " + select(selectAttrs) 
    13121326        } 
     1327 
     1328 
     1329        def UserSelectElement = { attrs, body -> 
     1330                // render list with publications currently available 
     1331                baseElement.call( 
     1332                        '_userList', 
     1333                        attrs, 
     1334                        body 
     1335                ) 
     1336 
     1337                attrs.description = ''; 
     1338 
     1339                // render 'Add user button' 
     1340                baseElement.call( 
     1341                        '_userAddButton', 
     1342                        attrs, 
     1343                        body 
     1344                ) 
     1345        } 
     1346 
     1347        /** 
     1348         * Renders an input box for publications 
     1349         */ 
     1350        def userSelect = { attrs, body -> 
     1351                if (attrs.get('value') == null) { 
     1352                        attrs.value = []; 
     1353                } 
     1354                if (attrs.get('description') == null) { 
     1355                        attrs.description = ''; 
     1356                } 
     1357                 
     1358                out << '<form id="' + attrs.name + '_form" onSubmit="return false;">'; 
     1359                out << select( 
     1360                        name: attrs.get("name"), 
     1361                        value: '', 
     1362                        from: SecUser.list(), 
     1363                        optionValue: 'username', 
     1364                        optionKey: 'id', 
     1365                        style: 'width: 400px;' 
     1366                ); 
     1367                out << '</form>'; 
     1368        } 
     1369 
     1370        def _userList = { attrs, body -> 
     1371                def display_none = 'none'; 
     1372                if (!attrs.get('value') || attrs.get('value').size() == 0) { 
     1373                        display_none = 'inline'; 
     1374                } 
     1375 
     1376                // Add a unordered list 
     1377                out << '<ul class="user_list" id="' + attrs.name + '_list">'; 
     1378 
     1379                out << '<li>'; 
     1380                out << '<span class="user_none" id="' + attrs.name + '_none" style="display: ' + display_none + ';">'; 
     1381                out << '-'; 
     1382                out << '</span>'; 
     1383                out << '</li>'; 
     1384 
     1385                out << '</ul>'; 
     1386 
     1387                // Add the publications using javascript 
     1388                out << '<script type="text/javascript">' 
     1389                if (attrs.get('value') && attrs.get('value').size() > 0) { 
     1390                        def i = 0; 
     1391                        attrs.get('value').each { 
     1392                                out << 'showUser( '; 
     1393                                out << '  "' + attrs.name + '",'; 
     1394                                out << '  ' + it.id + ','; 
     1395                                out << '  "' + it.username + '",'; 
     1396                                out << '  ' + i++; 
     1397                                out << ');'; 
     1398                        } 
     1399                } 
     1400                out << '</script>'; 
     1401 
     1402                def ids; 
     1403                if (attrs.get('value') && attrs.get('value').size() > 0) { 
     1404                        ids = attrs.get('value').id.join(',') 
     1405                } else { 
     1406                        ids = ''; 
     1407                } 
     1408                out << '<input type="hidden" name="' + attrs.name + '_ids" value="' + ids + '" id="' + attrs.name + '_ids">'; 
     1409        } 
     1410 
     1411        def _userAddButton = { attrs, body -> 
     1412 
     1413                // Output the dialog for the publications 
     1414                out << '<div id="' + attrs.name + '_dialog">'; 
     1415                out << '<p>Select a user from the database.</p>'; 
     1416                out << userSelect(attrs, body); 
     1417                out << '</div>'; 
     1418                out << '<script type="text/javascript">'; 
     1419                out << '  createUserDialog( "' + attrs.name + '" );' 
     1420                out << '</script>'; 
     1421 
     1422                out << '<input type="button" onClick="openUserDialog(\'' + attrs.name + '\' );" value="Add User">'; 
     1423        } 
     1424 
    13131425} 
  • trunk/grails-app/views/common/_login_panel.gsp

    r959 r976  
    1414                                        <h1>Member Login</h1> 
    1515                                        <label class="grey" for="username">Username:</label> 
    16                                         <input class="field" type="text" name="j_username" id="username" value="${username}" size="23" /> 
     16                                        <input class="field" type="text" name="j_username" id="j_username" value="${username}" size="23" /> 
    1717                                        <label class="grey" for="password">Password:</label> 
    1818                                        <input class="field" type="password" name="j_password" id="password" size="23" /> 
    19                                         <label><input type='checkbox' class='chk' name='${rememberMeParameter}' id='remember_me' 
    20                                         <g:if test='${hasCookie}'>checked='checked'</g:if> /></label> 
    21                 <div class="clear"></div> 
     19                                        <label><input type='checkbox' class='chk' name='_spring_security_remember_me' id='remember_me' 
     20                                        <g:if test='${hasCookie}'>checked='checked'</g:if> /> Remember me</label> 
     21                                        <div class="clear"></div> 
    2222                                        <input type="submit" name="submit" value="Login" class="bt_login" /> 
    23                                         <a class="lost-pwd" href="<g:createLink url="[action:'passwordReminder',controller:'auth']" class="lost-pwd" />">Lost your password?</a> 
     23                                        <a class="lost-pwd" href="<g:createLink url="[action:'forgotPassword',controller:'register']" class="lost-pwd" />">Lost your password?</a> 
    2424                                </g:form> 
    2525                        </div> 
    2626                        <div class="left right"> 
    27                                 <g:form url="[action:'signUp',controller:'auth']" class="clearfix"> 
     27                          <g:form url="[action:'add',controller:'userRegistration']" class="clearfix"> 
    2828                                        <input type="hidden" name="targetUri" value="${targetUri}" /> 
    2929                                        <h1>Not a member yet? Sign Up!</h1> 
    3030                                        <label class="grey" for="signup">Username:</label> 
    31                                         <input class="field" type="text" name="signup" id="signup" value="${username}" size="23" /> 
     31                                        <input class="field" type="text" name="username" id="username" value="${username}" size="23" /> 
    3232                                        <label class="grey" for="email">Email:</label> 
    3333                                        <input class="field" type="text" name="email" id="email" value="${email}" size="23" /> 
    34                                         <label>A password will be e-mailed to you.</label> 
     34                                        <label>A password will be e-mailed to you</label> 
     35 
    3536                                        <input type="submit" name="submit" value="Register" class="bt_register" /> 
    36                                 </g:form> 
     37                            </g:form> 
    3738                        </div> 
    3839                </div> 
     
    4142                <ul class="login"> 
    4243                        <li class="left">&nbsp;</li> 
    43                         <li>Hello <authentication:isLoggedIn><authentication:loggedInUsername/></authentication:isLoggedIn> 
    44                         <authentication:isNotLoggedIn>Guest</authentication:isNotLoggedIn>!</li> 
     44                        <li>Hello <sec:ifLoggedIn><sec:username/></sec:ifLoggedIn> 
     45                        <sec:ifNotLoggedIn>Guest</sec:ifNotLoggedIn>!</li> 
    4546                        <li class="sep">|</li> 
    4647                        <li id="toggle"> 
    47                         <authentication:isLoggedIn><g:link controller="logout" action="index">sign out</g:link></authentication:isLoggedIn> 
    48                                 <authentication:isNotLoggedIn> 
    49                                 <a id="open" class="open" href="#">Log In | Register</a> 
    50                                 <a id="close" style="display: none;" class="close" href="#">Close Panel</a> 
    51                                 </authentication:isNotLoggedIn> 
     48                        <sec:ifLoggedIn><g:link controller="logout" action="index">sign out</g:link></sec:ifLoggedIn> 
     49                            <sec:ifNotLoggedIn> 
     50                            <a id="open" class="open" href="#">Log In | Register</a> 
     51                            <a id="close" style="display: none;" class="close" href="#">Close Panel</a> 
     52                            </sec:ifNotLoggedIn> 
    5253                        </li> 
    5354            <li class="right">&nbsp;</li> 
  • trunk/grails-app/views/common/_topnav.gsp

    r959 r976  
    33    <ul class="topnav"> 
    44     <li><g:link controller="home" action="index">Home</g:link></li> 
    5 <n:isLoggedIn> 
    6      <li><g:link controller="study" action="list">My studies</g:link></li> 
    7 </n:isLoggedIn>     <li> 
     5<sec:ifLoggedIn> 
     6     <li><g:link controller="study" action="myStudies">My studies</g:link></li> 
     7</sec:ifLoggedIn>     <li> 
    88      <a href="#">Studies</a> 
    99      <ul class="subnav"> 
     
    3838     </li> 
    3939        </g:if> 
    40 <n:isAdministrator> 
     40   <sec:ifAllGranted roles="ROLE_ADMIN"> 
    4141     <li> 
    4242          <a href="#">User administation</a> 
    4343          <ul class="subnav"> 
    44             <li><g:link controller="admins" action="index" class="icon icon_user_suit">Manage Administrators</g:link></li> 
    45             <li><g:link controller="user" action="list" class="icon icon_user">List Users</g:link></li> 
     44            <li><g:link controller="user" class="icon icon_user">List Users</g:link></li> 
    4645            <li><g:link controller="user" action="create" class="icon icon_user_add">Create User</g:link></li> 
    47             <li><g:link controller="role" action="list" class="icon icon_cog">List Roles</g:link></li> 
     46            <li><g:link controller="role" class="icon icon_cog">List Roles</g:link></li> 
    4847            <li><g:link controller="role" action="create" class="icon icon_cog_add">Create Role</g:link></li> 
    49             <li><g:link controller="group" action="list" class="icon icon_group">List Groups</g:link></li> 
    50             <li><g:link controller="group" action="create" class="icon icon_group_add">Create Group</g:link></li> 
    51             <li><g:link controller="auth" action="logout" class="icon icon_cross">Sign out</g:link></li> 
     48            <li><g:link controller="logout" class="icon icon_cross">Sign out</g:link></li> 
    5249          </ul> 
    5350     </li> 
    54 </n:isAdministrator> 
     51   </sec:ifAllGranted> 
    5552    </ul> 
    5653    <!-- /TOPNAV //--> 
  • trunk/grails-app/views/study/list.gsp

    r959 r976  
    7777    </div> 
    7878    <div class="buttons"> 
    79                 <span class="button"><g:link class="create" controller="wizard" params="[jump:'create']"><g:message code="default.new.label" args="[entityName]" /></g:link></span> 
     79      <sec:ifLoggedIn> 
     80        <span class="button"><g:link class="create" controller="wizard" params="[jump:'create']"><g:message code="default.new.label" args="[entityName]" /></g:link></span> 
     81      </sec:ifLoggedIn> 
    8082    </div> 
    8183    <div class="paginateButtons"> 
  • trunk/grails-app/views/study/show.gsp

    r968 r976  
    273273              </g:each> 
    274274            </tr> 
    275                           <tr> 
    276                                 <td>Owner</td> 
    277                                 <g:each in="${studyList}" var="studyInstance"> 
    278                                   <td> 
    279                                         <g:if test="${studyInstance.owner}"> 
    280                                                 <g:link controller="user" action="show" id="${studyInstance.owner.id}">${studyInstance.owner?.encodeAsHTML()}</g:link> 
    281                                         </g:if> 
    282                                         <g:else> 
    283                                          - 
    284                                         </g:else> 
    285                                   </td> 
    286                                 </g:each> 
    287                           </tr> 
    288275            <tr> 
    289               <td>Members</td> 
     276              <td>Public</td> 
    290277              <g:each in="${studyList}" var="studyInstance"> 
    291278                <td> 
    292                   <% /* <g:if test="${studyInstance.getAllMemberUsers()==0}"> 
     279                   ${studyInstance.publicstudy} 
     280                </td> 
     281              </g:each> 
     282            </tr> 
     283            <tr> 
     284              <td>Owner</td> 
     285              <g:each in="${studyList}" var="studyInstance"> 
     286                <td> 
     287                   ${studyInstance.owner.username} 
     288                </td> 
     289              </g:each> 
     290            </tr> 
     291            <tr> 
     292              <td>Readers</td> 
     293              <g:each in="${studyList}" var="studyInstance"> 
     294                <td> 
     295                  <g:if test="${studyInstance.readers.size() == 0}"> 
    293296                    - 
    294297                  </g:if> 
    295298                  <g:else> 
    296                     <g:each in="${studyInstance.getAllMemberUsers()}" var="memberuser" status="i"> 
    297                       <g:if test="${i > 0}">, </g:if> 
    298                       <g:link controller="user" action="show" id="${memberuser.id}">${memberuser?.encodeAsHTML()}</g:link> 
    299                     </g:each> 
     299                    ${studyInstance.readers.username.join( ", " )} 
    300300                  </g:else> 
    301                   */ %> 
     301                </td> 
     302              </g:each> 
     303            </tr> 
     304            <tr> 
     305              <td>Writers</td> 
     306              <g:each in="${studyList}" var="studyInstance"> 
     307                <td> 
     308                  <g:if test="${studyInstance.writers.size()==0}"> 
     309                    - 
     310                  </g:if> 
     311                  <g:else> 
     312                    ${studyInstance.writers.username.join( ", " )} 
     313                  </g:else> 
    302314                </td> 
    303315              </g:each> 
     
    845857          <g:set var="studyInstance" value="${studyList[0]}" /> 
    846858          <g:hiddenField name="id" value="${studyInstance?.id}" /> 
    847           <span class="button"><g:link class="edit" controller="wizard" params="[jump:'edit']" id="${studyInstance?.id}">${message(code: 'default.button.edit.label', default: 'Edit')}</g:link></span> 
    848           <span class="button"><g:actionSubmit class="delete" action="delete" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" /></span> 
    849         </g:if> 
     859          <g:if test="${studyInstance.canWrite(loggedInUser)}"> 
     860            <span class="button"><g:link class="edit" controller="wizard" params="[jump:'edit']" id="${studyInstance?.id}">${message(code: 'default.button.edit.label', default: 'Edit')}</g:link></span> 
     861          </g:if> 
     862          <g:if test="${studyInstance.isOwner(loggedInUser)}"> 
     863            <span class="button"><g:actionSubmit class="delete" action="delete" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" /></span> 
     864          </g:if> 
     865          </g:if> 
    850866        <span class="button"><g:link class="backToList" action="list">Back to list</g:link></span> 
    851867      </g:form> 
  • trunk/grails-app/views/wizard/pages/_study.gsp

    r959 r976  
    3131        <wizard:publicationSelectElement name="publication" value="${study?.publications}" /> 
    3232        <wizard:contactSelectElement name="contacts" value="${study?.persons}" /> 
    33         </g:if> 
     33 
     34        <br /> 
     35        <div class="element"> 
     36          <div class="description">Public </div> 
     37          <div class="input"><g:checkBox name="publicstudy" value="${study?.publicstudy}" /></div> 
     38        </div> 
     39 
     40        <wizard:userSelectElement name="readers" description="Readers" value="${study?.readers}" /> 
     41        <wizard:userSelectElement name="writers" description="Writers" value="${study?.writers}" /> 
     42 
     43        </g:if> 
    3444 
    3545</wizard:pageContent> 
  • trunk/web-app/css/wizard.css

    r959 r976  
    385385} 
    386386 
    387 .wizard ul.publication_list, .wizard ul.contact_list { 
     387.wizard ul.publication_list, .wizard ul.contact_list, .wizard ul.user_list { 
    388388    list-style-type: none; 
    389389    margin: -10px 0 0 255px; 
     
    391391} 
    392392 
    393 .wizard ul.publication_list li, .wizard ul.contact_list li { 
     393.wizard ul.publication_list li, .wizard ul.contact_list li, .wizard ul.user_list li { 
    394394    margin-left: 0px; 
    395395    padding: 4px 6px; 
  • trunk/web-app/js/wizard.js

    r959 r976  
    382382} 
    383383 
    384  
    385384/************************************************* 
    386385 * 
     
    674673} 
    675674 
     675/************************************************* 
     676 * 
     677 * Functions for adding users (readers or writers) to the study 
     678 * 
     679 ************************************************/ 
     680 
     681/** 
     682 * Adds a user to the study using javascript 
     683 */ 
     684function addUser( element_id ) { 
     685    /* Find publication ID and add to form */ 
     686    id = parseInt( $("#" + element_id + "_form select").val() ); 
     687 
     688    // Put the ID in the array, but only if it does not yet exist 
     689    var ids = getUserIds( element_id ); 
     690 
     691    if( $.inArray (id, ids ) == -1 ) { 
     692        ids[ ids.length ] = id; 
     693        $( '#' + element_id + '_ids' ).val( ids.join( ',' ) ); 
     694 
     695        // Show the title and a remove button 
     696        showUser( element_id, id, $("#" + element_id + "_form select option:selected").text(), ids.length - 1 ); 
     697 
     698        // Hide the 'none box' 
     699        $( '#' + element_id + '_none' ).css( 'display', 'none' ); 
     700    } 
     701 
     702    return false; 
     703} 
     704 
     705/** 
     706 * Removes a user from the study using javascript 
     707 * N.B. The deletion must be handled in grails when the form is submitted 
     708 */ 
     709function removeUser( element_id, id ) { 
     710    var ids = getUserIds( element_id ); 
     711    if( $.inArray(id, ids ) != -1 ) { 
     712        // Remove the ID 
     713        ids.splice($.inArray(id, ids ), 1); 
     714        $( '#' + element_id + '_ids' ).val( ids.join( ',' ) ); 
     715 
     716        // Remove the title from the list 
     717        var li = $( "#" + element_id + '_item_' + id ); 
     718        if( li ) { 
     719            li.remove(); 
     720        } 
     721 
     722        // Show the 'none box' if needed 
     723        if( ids.length == 0 ) { 
     724            $( '#' + element_id + '_none' ).css( 'display', 'inline' ); 
     725        } 
     726 
     727    } 
     728} 
     729 
     730/** 
     731 * Returns an array of user IDs currently attached to the study 
     732 * The array contains integers 
     733 */ 
     734function getUserIds( element_id ) { 
     735    var ids = $( '#' + element_id + '_ids' ).val(); 
     736    if( ids == "" ) { 
     737        return new Array(); 
     738    } else { 
     739        ids_array = ids.split( ',' ); 
     740        for( var i = 0; i < ids_array.length; i++ ) { 
     741            ids_array[ i ] = parseInt( ids_array[ i ] ); 
     742        } 
     743 
     744        return ids_array; 
     745    } 
     746} 
     747 
     748/** 
     749 * Shows a publication on the screen 
     750 */ 
     751function showUser( element_id, id, username, nr ) { 
     752    var deletebutton = document.createElement( 'img' ); 
     753    deletebutton.className = 'famfamfam delete_button'; 
     754    deletebutton.setAttribute( 'alt', 'remove this user' ); 
     755    deletebutton.setAttribute( 'src', baseUrl + '/images/icons/famfamfam/delete.png' ); 
     756    deletebutton.onclick = function() { removeUser(  element_id, id ); return false; }; 
     757 
     758    var titleDiv = document.createElement( 'div' ); 
     759    titleDiv.className = 'username' ; 
     760    titleDiv.appendChild( document.createTextNode( username ) ); 
     761 
     762    var li = document.createElement( 'li' ); 
     763    li.setAttribute( 'id', element_id + '_item_' + id ); 
     764    li.className = nr % 2 == 0 ? 'even' : 'odd'; 
     765    li.appendChild( deletebutton ); 
     766    li.appendChild( titleDiv ); 
     767 
     768    $( '#' + element_id + '_list' ).append( li ); 
     769} 
     770 
     771/** 
     772 * Creates the dialog for searching a publication 
     773 */ 
     774function createUserDialog( element_id ) { 
     775    /* Because of the AJAX loading of this page, the dialog will be created 
     776     * again, when the page is reloaded. This raises problems when reading the 
     777     * values of the selected publication. For that reason we check whether the 
     778     * dialog already exists 
     779     */ 
     780    if( $( "." + element_id + "_user_dialog" ).length == 0 ) { 
     781        $("#" + element_id + "_dialog").dialog({ 
     782            title   : "Add user", 
     783            autoOpen: false, 
     784            width   : 800, 
     785            height  : 400, 
     786            modal   : true, 
     787            dialogClass : element_id + "_user_dialog", 
     788            position: "center", 
     789            buttons : { 
     790               Add  : function() { addUser( element_id ); $(this).dialog("close"); }, 
     791               Close  : function() { $(this).dialog("close"); } 
     792            }, 
     793            close   : function() { 
     794                /* closeFunc(this); */ 
     795            } 
     796        }).width(790).height(400); 
     797    } else { 
     798       /* If a dialog already exists, remove the new div */ 
     799       $("#" + element_id + "_dialog").remove(); 
     800    } 
     801} 
     802 
     803/** 
     804 * Opens the dialog for searching a publication 
     805 */ 
     806function openUserDialog( element_id ) { 
     807    // Empty input field 
     808    var field = $( '#' + element_id ); 
     809    field.val( '' ); 
     810 
     811    // Show the dialog 
     812    $( '#' + element_id + '_dialog' ).dialog( 'open' ); 
     813    field.focus(); 
     814 
     815    // Disable 'Add' button 
     816    //enableButton( '.' + element_id + '_user_dialog', 'Add', false ); 
     817}