Changeset 2188

Show
Ignore:
Timestamp:
28-03-12 19:09:01 (2 years ago)
Author:
work@…
Message:

- added / improved api documentation
- added getAssaysForStudy api method
- simplified and centralized simplication method for flattening domain data

Location:
trunk/grails-app
Files:
3 modified

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/controllers/api/ApiController.groovy

    r2186 r2188  
    3737 
    3838        // see if we already have a token on file for this device id 
    39         def token = Token.findByDeviceID(params.deviceID) 
    40  
     39        def token = Token.findByDeviceID(params?.deviceID) 
     40         
    4141        // generate a new token if we don't have a token on file 
    4242        def result = [:] 
     
    5252                ).save(failOnError: true) 
    5353            } 
    54  
     54             
    5555            result = ['token':token.deviceToken,'sequence':token.sequence] 
    5656 
     
    7272    } 
    7373 
    74 //    @Secured(['ROLE_CLIENT', 'ROLE_ADMIN']) 
    7574    def getStudies = { 
    7675        println "api::getStudies: ${params}" 
     
    8382            response.sendError(401, 'Unauthorized') 
    8483        } else { 
    85 //            def user = authenticationService.getLoggedInUser() 
    86             def user = Token.findByDeviceID(deviceID).user 
     84            def user = Token.findByDeviceID(deviceID)?.user 
    8785            def readableStudies = Study.giveReadableStudies(user) 
    8886            def studies = [] 
     
    9795                        'subjects'              : study.subjects.size(), 
    9896                        'species'               : study.subjects.species.collect { it.name }.unique(), 
    99                         'assays'                : study.assays.collect { it.module.name }.unique(), 
     97                        'assays'                : study.assays.collect { it.name }.unique(), 
     98                        'modules'               : study.assays.collect { it.module.name }.unique(), 
    10099                        'events'                : study.events.size(), 
    101100                        'uniqueEvents'          : study.events.collect { it.toString() }.unique(), 
     
    107106                ] 
    108107            } 
    109              
    110108 
    111109            def result = [ 
     
    126124    } 
    127125 
    128 //    @Secured(['ROLE_CLIENT', 'ROLE_ADMIN']) 
    129126    def getSubjectsForStudy = { 
    130127        println "api::getSubjectsForStudy: ${params}" 
     
    135132 
    136133        // fetch user and study 
    137 //        def user    = authenticationService.getLoggedInUser() 
    138         def user    = Token.findByDeviceID(deviceID).user 
     134        def user    = Token.findByDeviceID(deviceID)?.user 
    139135        def study   = Study.findByStudyUUID(studyToken) 
    140136         
     
    147143            response.sendError(401, 'Unauthorized') 
    148144        } else { 
    149             def subjects = [] 
    150              
    151             // iterate through subjects 
    152             study.subjects.each { 
    153                 def fields  = it.giveFields() 
    154                 def subject = [:] 
    155  
    156                 // add subject id 
    157                 subject['id'] = it.id 
    158  
    159                 // add subject field values 
    160                 fields.each { field -> 
    161                     def value = it.getFieldValue( field.name ) 
    162  
    163                     if (value.hasProperty('name')) { 
    164                         subject[ field.name ] = value.name 
    165                     } else { 
    166                         subject[ field.name ] = value 
    167                     } 
    168                 } 
    169  
    170                 subjects[ subjects.size() ] = subject 
    171             } 
    172              
     145            def subjects = apiService.flattenDomainData( study.subjects ) 
     146 
    173147            // define result 
    174148            def result = [ 
    175                     'count'     : study.subjects.size(), 
     149                    'count'     : subjects.size(), 
    176150                    'subjects'  : subjects 
    177151            ] 
     
    189163    } 
    190164 
    191  
    192 //    @Secured(['ROLE_CLIENT', 'ROLE_ADMIN']) 
    193165    def getAssaysForStudy = { 
    194166        println "api::getAssaysForStudy: ${params}" 
     
    199171 
    200172        // fetch user and study 
    201 //        def user    = authenticationService.getLoggedInUser() 
    202         def user    = Token.findByDeviceID(deviceID).user 
     173        def user    = Token.findByDeviceID(deviceID)?.user 
    203174        def study   = Study.findByStudyUUID(studyToken) 
    204175 
    205176        // check 
    206177        if (!apiService.validateRequest(deviceID,validation)) { 
    207             println "1" 
    208178            response.sendError(401, 'Unauthorized') 
    209179        } else if (!study) { 
    210             println "2" 
    211180            response.sendError(400, 'No such study') 
    212181        } else if (!study.canRead(user)) { 
    213             println "3" 
    214             response.sendError(401, 'Unauthorized') 
    215         } else { 
     182            response.sendError(401, 'Unauthorized') 
     183        } else { 
     184            def assays = apiService.flattenDomainData( study.assays ) 
     185             
    216186            // define result 
    217187            def result = [ 
    218 //                    'count'     : study.subjects.size(), 
    219 //                    'subjects'  : subjects 
     188                    'count'     : assays.size(), 
     189                    'assays'    : assays 
    220190            ] 
    221191 
  • trunk/grails-app/services/api/ApiService.groovy

    r2184 r2188  
    5050        } 
    5151    } 
     52     
     53    def flattenDomainData(elements) { 
     54        def items = [] 
     55 
     56        // iterate through elements 
     57        elements.each { 
     58            def fields  = it.giveFields() 
     59            def item    = [:] 
     60 
     61            // add token 
     62            if (it.respondsTo('getToken')) { 
     63                item['token'] = it.getToken() 
     64            } else { 
     65                item['id'] = it.id 
     66            } 
     67 
     68            // add subject field values 
     69            fields.each { field -> 
     70                def value = it.getFieldValue( field.name ) 
     71 
     72                if (value.hasProperty('name')) { 
     73                    item[ field.name ] = value.name 
     74                } else { 
     75                    item[ field.name ] = value 
     76                } 
     77            } 
     78 
     79            items[ items.size() ] = item 
     80        } 
     81 
     82        return items 
     83    } 
    5284} 
  • trunk/grails-app/views/api/index.gsp

    r2186 r2188  
    22<head> 
    33    <meta name="layout" content="main"/> 
     4    <style type="text/css"> 
     5        .api { 
     6            margin-top: -40px; 
     7        } 
     8 
     9        .api .header { 
     10            color: #ffda27; 
     11            font-size: 24px; 
     12            height: 40px; 
     13        } 
     14 
     15        .api h1 { 
     16            background-color: #006DBA; 
     17            padding-left: 10px; 
     18            margin-top: 40px; 
     19            height: 30px; 
     20            padding-top: 10px; 
     21            color: #fff; 
     22            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.68); 
     23        } 
     24 
     25        .api h2 { 
     26            font-size: 12px; 
     27            background-color: #d7e6f1; 
     28            padding-left: 10px; 
     29            margin-top: 10px; 
     30            height: 20px; 
     31            padding-top: 5px; 
     32            font-weight: bold; 
     33            color: #006DBA; 
     34            text-shadow: 0 1px 1px rgba(0, 0, 0, 0.28); 
     35        } 
     36 
     37        .api h3 { 
     38            font-size: 12px; 
     39            font-weight: bold; 
     40            color: #ee7624; 
     41            text-shadow: 0 1px 1px rgba(0, 0, 0, 0.28); 
     42        } 
     43         
     44        .api li { 
     45            margin-left: 30px; 
     46        } 
     47    </style> 
    448</head> 
    549<body> 
    6 <h1>API specification</h1> 
     50<div class="api"> 
     51<h1 class="header">API specification</h1> 
    752 
    853The API allows third party software to interface with GSCF and connected modules. 
    954 
    1055<h2>prerequisites</h2> 
    11     <li>a valid username / password</li> 
    12     <li>the username should be given the role ROLE_CLIENT</li> 
    13     <li>a shared secret</li> 
     56    <li>a valid username / password with role ROLE_CLIENT (see <a href="#authenticate">authenticate</a>)</li> 
     57    <li>a shared secret (used to calculate the validation md5 hash)</li> 
    1458    <li>a deviceID / clientID (look <a href="https://github.com/4np/UIDevice-with-UniqueIdentifier-for-iOS-5" target="_new">here</a> for iOS)</li> 
    1559 
     60<h2>available API calls</h2> 
     61    <li><a href="#authenticate">authenticate</a> - set up / synchronize client-server session</li> 
     62    <li><a href="#getStudies">getStudies</a> - fetch all (readable) studies</li> 
     63    <li><a href="#getSubjectsForStudy">getSubjectsForStudy</a> - fetch all subjects in a given study</li> 
     64    <li><a href="#getAssaysForStudy">getAssaysForStudy</a> - fetch all assays in a given study</li> 
     65 
     66<a name="authenticate"></a> 
    1667<h1>authenticate</h1> 
     68<h3>url: <g:createLink controller="api" action="authenticate" absolute="true" /></h3> 
    1769<p> 
    1870    Authenticate a client using <a href="http://en.wikipedia.org/wiki/Basic_access_authentication" target="_new">HTTP BASIC authentication</a>. 
    19     After successful authentication, a session token is returned which should be used in all subsequent calls to authorize the API calls. 
     71    This API call is used to: 
     72    <li>initially set up a client/server session</li> 
     73    <li>re-synchronise client/server sessions that become out of sync (e.g. <i>sequence</i> differences)</li> 
     74<p> 
     75 
     76<p> 
     77    After successful authentication, a session token is returned which should the client should store locally. This session token 
     78    should be used in all subsequent calls to calculate the validation md5 hash. 
     79</p> 
     80<p> 
    2081    This call should also be performed whenever a client/server sessions becomes out of sync (e.g. the client's sequence count 
    21     differs from the server's sequence count) as the server's sequence count will be returned. For security reasons this api method is 
    22     designed to be called only once (or when sessions are out of sync) as HTTP BASIC authentication is not really secure (if someone 
    23     is able to sniff your traffic, the authentication md5 hash is easily stolen).<br/> 
    24     Every subsequent request the client does, needs to contain a validation MD5 hash, which is a MD5 sum of the concatenation of the device token, 
    25     the request sequence and a shared secret (e.g. <i>md5sum( token + sequence + shared secret )</i>). 
     82    differs from the server's sequence count) as the server's sequence count will be returned after successfully authenticating. 
     83    For security reasons this api method is designed to be called only once (or when sessions are out of sync) as HTTP BASIC authentication 
     84    is not really secure (if someone is able to sniff your traffic, the authentication md5 hash is easily stolen). API calls are 
     85    validated using the calculated md5 hash. 
     86</p> 
     87<p> 
     88    Every subsequent request the client does, needs to contain the validation MD5 hash, which is a MD5 sum of the concatenation of the device token, 
     89    the request sequence and a shared secret (e.g. <i>md5sum( token + sequence + shared secret )</i> ).<br/> 
    2690    <i>Note that in order to be able to successfully authenticate or use the API in general, the user should have the ROLE_CLIENT assigned!</i> 
    2791 
     
    40104            <td>string</td> 
    41105            <td>32</td> 
    42             <td>a unique ID of the client device / application performing the call</td> 
     106            <td>a unique ID of the client device / application performing the call (<a href="https://github.com/4np/UIDevice-with-UniqueIdentifier-for-iOS-5" target="_new">iOS example</a>)</td> 
    43107            <td>9ae87836-d38d-4b86-be6a-eff93f2b049a</td> 
    44108            <td>yes</td> 
     
    77141</p> 
    78142 
     143<a name="getStudies"></a> 
    79144<h1>getStudies</h1> 
     145<h3>url: <g:createLink controller="api" action="getStudies" absolute="true" /></h3> 
    80146<p> 
    81147    Returns the studies which are <i>readable</i> and/or <i>writable</i> for the client. If the client should get access to a particular 
     
    116182</p> 
    117183 
     184<a name="getSubjectsForStudy"></a> 
    118185<h1>getSubjectsForStudy</h1> 
     186<h3>url: <g:createLink controller="api" action="getSubjectsForStudy" absolute="true" /></h3> 
    119187<p> 
    120188    Returns the subjects for a particular study 
     
    161229    </blockquote> 
    162230</p> 
     231 
     232<a name="getAssaysForStudy"></a> 
     233<h1>getAssaysForStudy</h1> 
     234<h3>url: <g:createLink controller="api" action="getAssaysForStudy" absolute="true" /></h3> 
     235<p> 
     236    Returns the assays for a particular study 
     237 
     238    <h2>Request parameters</h2> 
     239    <table> 
     240        <thead> 
     241        <th>argument</th> 
     242        <th>type</th> 
     243        <th>length</th> 
     244        <th>description</th> 
     245        <th>example</th> 
     246        <th>required</th> 
     247        </thead> 
     248        <tr> 
     249            <td>deviceID</td> 
     250            <td>string</td> 
     251            <td>36 (max)</td> 
     252            <td>a unique ID of the client device / application performing the call</td> 
     253            <td>9ae87836-d38d-4b86-be6a-eff93f2b049a</td> 
     254            <td>yes</td> 
     255        </tr> 
     256        <tr> 
     257            <td>validation</td> 
     258            <td>string</td> 
     259            <td>-</td> 
     260            <td><a href="http://www.miraclesalad.com/webtools/md5.php" target="_new">md5sum</a>( token + sequence + shared secret )</td> 
     261            <td>9ae87836d38d4b86be6aeff93f2b049a</td> 
     262            <td>yes</td> 
     263        </tr> 
     264        <tr> 
     265            <td>studyToken</td> 
     266            <td>string</td> 
     267            <td>255</td> 
     268            <td>study token (see getStudies)</td> 
     269            <td>b6e0c6f4-d8db-4a43-91fa-a157d2d492f0</td> 
     270            <td>yes</td> 
     271        </tr> 
     272    </table> 
     273 
     274    <h2>example reply</h2> 
     275    <blockquote> 
     276        {"count":6,"assays":[{"token":"253ec24f-9bac-4f2b-b9cf-f84b86376a4e","name":"16S Sequencing assay","module":"Mass Sequencing module","Description":null},{"token":"4df2f49d-1d8c-48bd-8ebd-d267164948ec","name":"18S Sequencing assay","module":"Mass Sequencing module","Description":null},{"token":"828cf2d6-d797-484b-82f9-df9933d76d77","name":"Glucose assay after","module":"SAM module for clinical data","Description":null},{"token":"d68e8fed-41ca-4408-9d8e-f3598eca9183","name":"Glucose assay before","module":"SAM module for clinical data","Description":null},{"token":"32945764-6c5e-497c-8b1e-0d5e0dfa8221","name":"Lipidomics profile after","module":"Metabolomics module","Description":null,"Spectrometry technique":"GC/MS"},{"token":"92f42f77-1c13-4b25-aa57-b444e355fbf4","name":"Lipidomics profile before","module":"Metabolomics module","Description":null,"Spectrometry technique":"GC/MS"}]} 
     277    </blockquote> 
     278</p> 
     279</div> 
    163280</body> 
    164281</html>