Changeset 2188


Ignore:
Timestamp:
Mar 28, 2012, 7:09:01 PM (11 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 edited

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>
Note: See TracChangeset for help on using the changeset viewer.